// Copyright 2017 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "src/interpreter/interpreter-generator.h"

#include <array>
#include <tuple>

#include "src/builtins/builtins-arguments-gen.h"
#include "src/builtins/builtins-constructor-gen.h"
#include "src/builtins/builtins-iterator-gen.h"
#include "src/code-events.h"
#include "src/code-factory.h"
#include "src/debug/debug.h"
#include "src/ic/accessor-assembler.h"
#include "src/ic/binary-op-assembler.h"
#include "src/ic/ic.h"
#include "src/interpreter/bytecode-flags.h"
#include "src/interpreter/bytecodes.h"
#include "src/interpreter/interpreter-assembler.h"
#include "src/interpreter/interpreter-intrinsics-generator.h"
#include "src/objects-inl.h"
#include "src/objects/cell.h"
#include "src/objects/js-generator.h"
#include "src/objects/module.h"
#include "src/objects/oddball.h"
#include "src/ostreams.h"

namespace v8 {
namespace internal {
    namespace interpreter {

        namespace {

            using compiler::Node;
            using Label = CodeStubAssembler::Label;
            using Variable = CodeStubAssembler::Variable;

#define IGNITION_HANDLER(Name, BaseAssembler)                           \
    class Name##Assembler : public BaseAssembler {                      \
    public:                                                             \
        explicit Name##Assembler(compiler::CodeAssemblerState* state,   \
            Bytecode bytecode, OperandScale scale)                      \
            : BaseAssembler(state, bytecode, scale)                     \
        {                                                               \
        }                                                               \
        static void Generate(compiler::CodeAssemblerState* state,       \
            OperandScale scale);                                        \
                                                                        \
    private:                                                            \
        void GenerateImpl();                                            \
        DISALLOW_COPY_AND_ASSIGN(Name##Assembler);                      \
    };                                                                  \
    void Name##Assembler::Generate(compiler::CodeAssemblerState* state, \
        OperandScale scale)                                             \
    {                                                                   \
        Name##Assembler assembler(state, Bytecode::k##Name, scale);     \
        state->SetInitialDebugInformation(#Name, __FILE__, __LINE__);   \
        assembler.GenerateImpl();                                       \
    }                                                                   \
    void Name##Assembler::GenerateImpl()

            // LdaZero
            //
            // Load literal '0' into the accumulator.
            IGNITION_HANDLER(LdaZero, InterpreterAssembler)
            {
                Node* zero_value = NumberConstant(0.0);
                SetAccumulator(zero_value);
                Dispatch();
            }

            // LdaSmi <imm>
            //
            // Load an integer literal into the accumulator as a Smi.
            IGNITION_HANDLER(LdaSmi, InterpreterAssembler)
            {
                Node* smi_int = BytecodeOperandImmSmi(0);
                SetAccumulator(smi_int);
                Dispatch();
            }

            // LdaConstant <idx>
            //
            // Load constant literal at |idx| in the constant pool into the accumulator.
            IGNITION_HANDLER(LdaConstant, InterpreterAssembler)
            {
                Node* constant = LoadConstantPoolEntryAtOperandIndex(0);
                SetAccumulator(constant);
                Dispatch();
            }

            // LdaUndefined
            //
            // Load Undefined into the accumulator.
            IGNITION_HANDLER(LdaUndefined, InterpreterAssembler)
            {
                SetAccumulator(UndefinedConstant());
                Dispatch();
            }

            // LdaNull
            //
            // Load Null into the accumulator.
            IGNITION_HANDLER(LdaNull, InterpreterAssembler)
            {
                SetAccumulator(NullConstant());
                Dispatch();
            }

            // LdaTheHole
            //
            // Load TheHole into the accumulator.
            IGNITION_HANDLER(LdaTheHole, InterpreterAssembler)
            {
                SetAccumulator(TheHoleConstant());
                Dispatch();
            }

            // LdaTrue
            //
            // Load True into the accumulator.
            IGNITION_HANDLER(LdaTrue, InterpreterAssembler)
            {
                SetAccumulator(TrueConstant());
                Dispatch();
            }

            // LdaFalse
            //
            // Load False into the accumulator.
            IGNITION_HANDLER(LdaFalse, InterpreterAssembler)
            {
                SetAccumulator(FalseConstant());
                Dispatch();
            }

            // Ldar <src>
            //
            // Load accumulator with value from register <src>.
            IGNITION_HANDLER(Ldar, InterpreterAssembler)
            {
                Node* value = LoadRegisterAtOperandIndex(0);
                SetAccumulator(value);
                Dispatch();
            }

            // Star <dst>
            //
            // Store accumulator to register <dst>.
            IGNITION_HANDLER(Star, InterpreterAssembler)
            {
                Node* accumulator = GetAccumulator();
                StoreRegisterAtOperandIndex(accumulator, 0);
                Dispatch();
            }

            // Mov <src> <dst>
            //
            // Stores the value of register <src> to register <dst>.
            IGNITION_HANDLER(Mov, InterpreterAssembler)
            {
                Node* src_value = LoadRegisterAtOperandIndex(0);
                StoreRegisterAtOperandIndex(src_value, 1);
                Dispatch();
            }

            class InterpreterLoadGlobalAssembler : public InterpreterAssembler {
            public:
                InterpreterLoadGlobalAssembler(CodeAssemblerState* state, Bytecode bytecode,
                    OperandScale operand_scale)
                    : InterpreterAssembler(state, bytecode, operand_scale)
                {
                }

                void LdaGlobal(int slot_operand_index, int name_operand_index,
                    TypeofMode typeof_mode)
                {
                    Node* maybe_feedback_vector = LoadFeedbackVector();
                    Node* feedback_slot = BytecodeOperandIdx(slot_operand_index);

                    AccessorAssembler accessor_asm(state());
                    ExitPoint exit_point(this, [=](Node* result) {
                        SetAccumulator(result);
                        Dispatch();
                    });

                    LazyNode<Context> lazy_context = [=] { return CAST(GetContext()); };

                    LazyNode<Name> lazy_name = [=] {
                        Node* name = LoadConstantPoolEntryAtOperandIndex(name_operand_index);
                        return CAST(name);
                    };

                    ParameterMode slot_mode = CodeStubAssembler::INTPTR_PARAMETERS;
                    accessor_asm.LoadGlobalIC(maybe_feedback_vector, feedback_slot,
                        lazy_context, lazy_name, typeof_mode, &exit_point,
                        slot_mode);
                }
            };

            // LdaGlobal <name_index> <slot>
            //
            // Load the global with name in constant pool entry <name_index> into the
            // accumulator using FeedBackVector slot <slot> outside of a typeof.
            IGNITION_HANDLER(LdaGlobal, InterpreterLoadGlobalAssembler)
            {
                static const int kNameOperandIndex = 0;
                static const int kSlotOperandIndex = 1;

                LdaGlobal(kSlotOperandIndex, kNameOperandIndex, NOT_INSIDE_TYPEOF);
            }

            // LdaGlobalInsideTypeof <name_index> <slot>
            //
            // Load the global with name in constant pool entry <name_index> into the
            // accumulator using FeedBackVector slot <slot> inside of a typeof.
            IGNITION_HANDLER(LdaGlobalInsideTypeof, InterpreterLoadGlobalAssembler)
            {
                static const int kNameOperandIndex = 0;
                static const int kSlotOperandIndex = 1;

                LdaGlobal(kSlotOperandIndex, kNameOperandIndex, INSIDE_TYPEOF);
            }

            // StaGlobal <name_index> <slot>
            //
            // Store the value in the accumulator into the global with name in constant pool
            // entry <name_index> using FeedBackVector slot <slot>.
            IGNITION_HANDLER(StaGlobal, InterpreterAssembler)
            {
                Node* context = GetContext();

                // Store the global via the StoreGlobalIC.
                Node* name = LoadConstantPoolEntryAtOperandIndex(0);
                Node* value = GetAccumulator();
                Node* raw_slot = BytecodeOperandIdx(1);
                Node* smi_slot = SmiTag(raw_slot);
                Node* maybe_vector = LoadFeedbackVector();

                Label no_feedback(this, Label::kDeferred), end(this);
                GotoIf(IsUndefined(maybe_vector), &no_feedback);

                CallBuiltin(Builtins::kStoreGlobalIC, context, name, value, smi_slot,
                    maybe_vector);
                Goto(&end);

                Bind(&no_feedback);
                CallRuntime(Runtime::kStoreGlobalICNoFeedback_Miss, context, value, name);
                Goto(&end);

                Bind(&end);
                Dispatch();
            }

            // LdaContextSlot <context> <slot_index> <depth>
            //
            // Load the object in |slot_index| of the context at |depth| in the context
            // chain starting at |context| into the accumulator.
            IGNITION_HANDLER(LdaContextSlot, InterpreterAssembler)
            {
                Node* context = LoadRegisterAtOperandIndex(0);
                Node* slot_index = BytecodeOperandIdx(1);
                Node* depth = BytecodeOperandUImm(2);
                Node* slot_context = GetContextAtDepth(context, depth);
                Node* result = LoadContextElement(slot_context, slot_index);
                SetAccumulator(result);
                Dispatch();
            }

            // LdaImmutableContextSlot <context> <slot_index> <depth>
            //
            // Load the object in |slot_index| of the context at |depth| in the context
            // chain starting at |context| into the accumulator.
            IGNITION_HANDLER(LdaImmutableContextSlot, InterpreterAssembler)
            {
                Node* context = LoadRegisterAtOperandIndex(0);
                Node* slot_index = BytecodeOperandIdx(1);
                Node* depth = BytecodeOperandUImm(2);
                Node* slot_context = GetContextAtDepth(context, depth);
                Node* result = LoadContextElement(slot_context, slot_index);
                SetAccumulator(result);
                Dispatch();
            }

            // LdaCurrentContextSlot <slot_index>
            //
            // Load the object in |slot_index| of the current context into the accumulator.
            IGNITION_HANDLER(LdaCurrentContextSlot, InterpreterAssembler)
            {
                Node* slot_index = BytecodeOperandIdx(0);
                Node* slot_context = GetContext();
                Node* result = LoadContextElement(slot_context, slot_index);
                SetAccumulator(result);
                Dispatch();
            }

            // LdaImmutableCurrentContextSlot <slot_index>
            //
            // Load the object in |slot_index| of the current context into the accumulator.
            IGNITION_HANDLER(LdaImmutableCurrentContextSlot, InterpreterAssembler)
            {
                Node* slot_index = BytecodeOperandIdx(0);
                Node* slot_context = GetContext();
                Node* result = LoadContextElement(slot_context, slot_index);
                SetAccumulator(result);
                Dispatch();
            }

            // StaContextSlot <context> <slot_index> <depth>
            //
            // Stores the object in the accumulator into |slot_index| of the context at
            // |depth| in the context chain starting at |context|.
            IGNITION_HANDLER(StaContextSlot, InterpreterAssembler)
            {
                Node* value = GetAccumulator();
                Node* context = LoadRegisterAtOperandIndex(0);
                Node* slot_index = BytecodeOperandIdx(1);
                Node* depth = BytecodeOperandUImm(2);
                Node* slot_context = GetContextAtDepth(context, depth);
                StoreContextElement(slot_context, slot_index, value);
                Dispatch();
            }

            // StaCurrentContextSlot <slot_index>
            //
            // Stores the object in the accumulator into |slot_index| of the current
            // context.
            IGNITION_HANDLER(StaCurrentContextSlot, InterpreterAssembler)
            {
                Node* value = GetAccumulator();
                Node* slot_index = BytecodeOperandIdx(0);
                Node* slot_context = GetContext();
                StoreContextElement(slot_context, slot_index, value);
                Dispatch();
            }

            // LdaLookupSlot <name_index>
            //
            // Lookup the object with the name in constant pool entry |name_index|
            // dynamically.
            IGNITION_HANDLER(LdaLookupSlot, InterpreterAssembler)
            {
                Node* name = LoadConstantPoolEntryAtOperandIndex(0);
                Node* context = GetContext();
                Node* result = CallRuntime(Runtime::kLoadLookupSlot, context, name);
                SetAccumulator(result);
                Dispatch();
            }

            // LdaLookupSlotInsideTypeof <name_index>
            //
            // Lookup the object with the name in constant pool entry |name_index|
            // dynamically without causing a NoReferenceError.
            IGNITION_HANDLER(LdaLookupSlotInsideTypeof, InterpreterAssembler)
            {
                Node* name = LoadConstantPoolEntryAtOperandIndex(0);
                Node* context = GetContext();
                Node* result = CallRuntime(Runtime::kLoadLookupSlotInsideTypeof, context, name);
                SetAccumulator(result);
                Dispatch();
            }

            class InterpreterLookupContextSlotAssembler : public InterpreterAssembler {
            public:
                InterpreterLookupContextSlotAssembler(CodeAssemblerState* state,
                    Bytecode bytecode,
                    OperandScale operand_scale)
                    : InterpreterAssembler(state, bytecode, operand_scale)
                {
                }

                void LookupContextSlot(Runtime::FunctionId function_id)
                {
                    Node* context = GetContext();
                    Node* slot_index = BytecodeOperandIdx(1);
                    Node* depth = BytecodeOperandUImm(2);

                    Label slowpath(this, Label::kDeferred);

                    // Check for context extensions to allow the fast path.
                    GotoIfHasContextExtensionUpToDepth(context, depth, &slowpath);

                    // Fast path does a normal load context.
                    {
                        Node* slot_context = GetContextAtDepth(context, depth);
                        Node* result = LoadContextElement(slot_context, slot_index);
                        SetAccumulator(result);
                        Dispatch();
                    }

                    // Slow path when we have to call out to the runtime.
                    BIND(&slowpath);
                    {
                        Node* name = LoadConstantPoolEntryAtOperandIndex(0);
                        Node* result = CallRuntime(function_id, context, name);
                        SetAccumulator(result);
                        Dispatch();
                    }
                }
            };

            // LdaLookupSlot <name_index>
            //
            // Lookup the object with the name in constant pool entry |name_index|
            // dynamically.
            IGNITION_HANDLER(LdaLookupContextSlot, InterpreterLookupContextSlotAssembler)
            {
                LookupContextSlot(Runtime::kLoadLookupSlot);
            }

            // LdaLookupSlotInsideTypeof <name_index>
            //
            // Lookup the object with the name in constant pool entry |name_index|
            // dynamically without causing a NoReferenceError.
            IGNITION_HANDLER(LdaLookupContextSlotInsideTypeof,
                InterpreterLookupContextSlotAssembler)
            {
                LookupContextSlot(Runtime::kLoadLookupSlotInsideTypeof);
            }

            class InterpreterLookupGlobalAssembler : public InterpreterLoadGlobalAssembler {
            public:
                InterpreterLookupGlobalAssembler(CodeAssemblerState* state, Bytecode bytecode,
                    OperandScale operand_scale)
                    : InterpreterLoadGlobalAssembler(state, bytecode, operand_scale)
                {
                }

                void LookupGlobalSlot(Runtime::FunctionId function_id)
                {
                    Node* context = GetContext();
                    Node* depth = BytecodeOperandUImm(2);

                    Label slowpath(this, Label::kDeferred);

                    // Check for context extensions to allow the fast path
                    GotoIfHasContextExtensionUpToDepth(context, depth, &slowpath);

                    // Fast path does a normal load global
                    {
                        static const int kNameOperandIndex = 0;
                        static const int kSlotOperandIndex = 1;

                        TypeofMode typeof_mode = function_id == Runtime::kLoadLookupSlotInsideTypeof
                            ? INSIDE_TYPEOF
                            : NOT_INSIDE_TYPEOF;

                        LdaGlobal(kSlotOperandIndex, kNameOperandIndex, typeof_mode);
                    }

                    // Slow path when we have to call out to the runtime
                    BIND(&slowpath);
                    {
                        Node* name = LoadConstantPoolEntryAtOperandIndex(0);
                        Node* result = CallRuntime(function_id, context, name);
                        SetAccumulator(result);
                        Dispatch();
                    }
                }
            };

            // LdaLookupGlobalSlot <name_index> <feedback_slot> <depth>
            //
            // Lookup the object with the name in constant pool entry |name_index|
            // dynamically.
            IGNITION_HANDLER(LdaLookupGlobalSlot, InterpreterLookupGlobalAssembler)
            {
                LookupGlobalSlot(Runtime::kLoadLookupSlot);
            }

            // LdaLookupGlobalSlotInsideTypeof <name_index> <feedback_slot> <depth>
            //
            // Lookup the object with the name in constant pool entry |name_index|
            // dynamically without causing a NoReferenceError.
            IGNITION_HANDLER(LdaLookupGlobalSlotInsideTypeof,
                InterpreterLookupGlobalAssembler)
            {
                LookupGlobalSlot(Runtime::kLoadLookupSlotInsideTypeof);
            }

            // StaLookupSlotSloppy <name_index> <flags>
            //
            // Store the object in accumulator to the object with the name in constant
            // pool entry |name_index|.
            IGNITION_HANDLER(StaLookupSlot, InterpreterAssembler)
            {
                Node* value = GetAccumulator();
                Node* name = LoadConstantPoolEntryAtOperandIndex(0);
                Node* bytecode_flags = BytecodeOperandFlag(1);
                Node* context = GetContext();
                Variable var_result(this, MachineRepresentation::kTagged);

                Label sloppy(this), strict(this), end(this);
                DCHECK_EQ(0, LanguageMode::kSloppy);
                DCHECK_EQ(1, LanguageMode::kStrict);
                DCHECK_EQ(0, static_cast<int>(LookupHoistingMode::kNormal));
                DCHECK_EQ(1, static_cast<int>(LookupHoistingMode::kLegacySloppy));
                Branch(IsSetWord32<StoreLookupSlotFlags::LanguageModeBit>(bytecode_flags),
                    &strict, &sloppy);

                BIND(&strict);
                {
                    CSA_ASSERT(this, IsClearWord32<StoreLookupSlotFlags::LookupHoistingModeBit>(bytecode_flags));
                    var_result.Bind(
                        CallRuntime(Runtime::kStoreLookupSlot_Strict, context, name, value));
                    Goto(&end);
                }

                BIND(&sloppy);
                {
                    Label hoisting(this), ordinary(this);
                    Branch(IsSetWord32<StoreLookupSlotFlags::LookupHoistingModeBit>(
                               bytecode_flags),
                        &hoisting, &ordinary);

                    BIND(&hoisting);
                    {
                        var_result.Bind(CallRuntime(Runtime::kStoreLookupSlot_SloppyHoisting,
                            context, name, value));
                        Goto(&end);
                    }

                    BIND(&ordinary);
                    {
                        var_result.Bind(
                            CallRuntime(Runtime::kStoreLookupSlot_Sloppy, context, name, value));
                        Goto(&end);
                    }
                }

                BIND(&end);
                {
                    SetAccumulator(var_result.value());
                    Dispatch();
                }
            }

            // LdaNamedProperty <object> <name_index> <slot>
            //
            // Calls the LoadIC at FeedBackVector slot <slot> for <object> and the name at
            // constant pool entry <name_index>.
            IGNITION_HANDLER(LdaNamedProperty, InterpreterAssembler)
            {
                Node* feedback_vector = LoadFeedbackVector();
                Node* feedback_slot = BytecodeOperandIdx(2);
                Node* smi_slot = SmiTag(feedback_slot);

                // Load receiver.
                Node* recv = LoadRegisterAtOperandIndex(0);

                // Load the name.
                // TODO(jgruber): Not needed for monomorphic smi handler constant/field case.
                Node* name = LoadConstantPoolEntryAtOperandIndex(1);
                Node* context = GetContext();

                Label done(this);
                Variable var_result(this, MachineRepresentation::kTagged);
                ExitPoint exit_point(this, &done, &var_result);

                AccessorAssembler::LoadICParameters params(context, recv, name, smi_slot,
                    feedback_vector);
                AccessorAssembler accessor_asm(state());
                accessor_asm.LoadIC_BytecodeHandler(&params, &exit_point);

                BIND(&done);
                {
                    SetAccumulator(var_result.value());
                    Dispatch();
                }
            }

            // LdaPropertyNofeedback <object> <slot>
            //
            // Calls the GetProperty builtin for <object> and the key in the accumulator.
            IGNITION_HANDLER(LdaNamedPropertyNoFeedback, InterpreterAssembler)
            {
                Node* object = LoadRegisterAtOperandIndex(0);
                Node* name = LoadConstantPoolEntryAtOperandIndex(1);
                Node* context = GetContext();
                Node* result = CallBuiltin(Builtins::kGetProperty, context, object, name);
                SetAccumulator(result);
                Dispatch();
            }

            // KeyedLoadIC <object> <slot>
            //
            // Calls the KeyedLoadIC at FeedBackVector slot <slot> for <object> and the key
            // in the accumulator.
            IGNITION_HANDLER(LdaKeyedProperty, InterpreterAssembler)
            {
                Node* object = LoadRegisterAtOperandIndex(0);
                Node* name = GetAccumulator();
                Node* raw_slot = BytecodeOperandIdx(1);
                Node* smi_slot = SmiTag(raw_slot);
                Node* feedback_vector = LoadFeedbackVector();
                Node* context = GetContext();

                VARIABLE(var_result, MachineRepresentation::kTagged);
                var_result.Bind(CallBuiltin(Builtins::kKeyedLoadIC, context, object, name,
                    smi_slot, feedback_vector));
                SetAccumulator(var_result.value());
                Dispatch();
            }

            class InterpreterStoreNamedPropertyAssembler : public InterpreterAssembler {
            public:
                InterpreterStoreNamedPropertyAssembler(CodeAssemblerState* state,
                    Bytecode bytecode,
                    OperandScale operand_scale)
                    : InterpreterAssembler(state, bytecode, operand_scale)
                {
                }

                void StaNamedProperty(Callable ic, NamedPropertyType property_type)
                {
                    Node* code_target = HeapConstant(ic.code());
                    Node* object = LoadRegisterAtOperandIndex(0);
                    Node* name = LoadConstantPoolEntryAtOperandIndex(1);
                    Node* value = GetAccumulator();
                    Node* raw_slot = BytecodeOperandIdx(2);
                    Node* smi_slot = SmiTag(raw_slot);
                    Node* maybe_vector = LoadFeedbackVector();
                    Node* context = GetContext();

                    VARIABLE(var_result, MachineRepresentation::kTagged);
                    var_result.Bind(CallStub(ic.descriptor(), code_target, context, object,
                        name, value, smi_slot, maybe_vector));
                    // To avoid special logic in the deoptimizer to re-materialize the value in
                    // the accumulator, we overwrite the accumulator after the IC call. It
                    // doesn't really matter what we write to the accumulator here, since we
                    // restore to the correct value on the outside. Storing the result means we
                    // don't need to keep unnecessary state alive across the callstub.
                    SetAccumulator(var_result.value());
                    Dispatch();
                }
            };

            // StaNamedProperty <object> <name_index> <slot>
            //
            // Calls the StoreIC at FeedBackVector slot <slot> for <object> and
            // the name in constant pool entry <name_index> with the value in the
            // accumulator.
            IGNITION_HANDLER(StaNamedProperty, InterpreterStoreNamedPropertyAssembler)
            {
                Callable ic = Builtins::CallableFor(isolate(), Builtins::kStoreIC);
                StaNamedProperty(ic, NamedPropertyType::kNotOwn);
            }

            // StaNamedOwnProperty <object> <name_index> <slot>
            //
            // Calls the StoreOwnIC at FeedBackVector slot <slot> for <object> and
            // the name in constant pool entry <name_index> with the value in the
            // accumulator.
            IGNITION_HANDLER(StaNamedOwnProperty, InterpreterStoreNamedPropertyAssembler)
            {
                Callable ic = CodeFactory::StoreOwnICInOptimizedCode(isolate());
                StaNamedProperty(ic, NamedPropertyType::kOwn);
            }

            // StaNamedPropertyNoFeedback <object> <name_index>
            //
            // Calls the SetPropertyBuiltin for <object> and the name in constant pool entry
            // <name_index> with the value in the accumulator.
            IGNITION_HANDLER(StaNamedPropertyNoFeedback,
                InterpreterStoreNamedPropertyAssembler)
            {
                Node* object = LoadRegisterAtOperandIndex(0);
                Node* name = LoadConstantPoolEntryAtOperandIndex(1);
                Node* value = GetAccumulator();
                Node* context = GetContext();

                Node* result = CallRuntime(Runtime::kSetNamedProperty, context, object, name, value);
                SetAccumulator(result);
                Dispatch();
            }

            // StaKeyedProperty <object> <key> <slot>
            //
            // Calls the KeyedStoreIC at FeedbackVector slot <slot> for <object> and
            // the key <key> with the value in the accumulator.
            IGNITION_HANDLER(StaKeyedProperty, InterpreterAssembler)
            {
                Node* object = LoadRegisterAtOperandIndex(0);
                Node* name = LoadRegisterAtOperandIndex(1);
                Node* value = GetAccumulator();
                Node* raw_slot = BytecodeOperandIdx(2);
                Node* smi_slot = SmiTag(raw_slot);
                Node* maybe_vector = LoadFeedbackVector();
                Node* context = GetContext();

                VARIABLE(var_result, MachineRepresentation::kTagged);
                var_result.Bind(CallBuiltin(Builtins::kKeyedStoreIC, context, object, name,
                    value, smi_slot, maybe_vector));
                // To avoid special logic in the deoptimizer to re-materialize the value in
                // the accumulator, we overwrite the accumulator after the IC call. It
                // doesn't really matter what we write to the accumulator here, since we
                // restore to the correct value on the outside. Storing the result means we
                // don't need to keep unnecessary state alive across the callstub.
                SetAccumulator(var_result.value());
                Dispatch();
            }

            // StaInArrayLiteral <array> <index> <slot>
            //
            // Calls the StoreInArrayLiteralIC at FeedbackVector slot <slot> for <array> and
            // the key <index> with the value in the accumulator.
            IGNITION_HANDLER(StaInArrayLiteral, InterpreterAssembler)
            {
                Node* array = LoadRegisterAtOperandIndex(0);
                Node* index = LoadRegisterAtOperandIndex(1);
                Node* value = GetAccumulator();
                Node* raw_slot = BytecodeOperandIdx(2);
                Node* smi_slot = SmiTag(raw_slot);
                Node* feedback_vector = LoadFeedbackVector();
                Node* context = GetContext();

                VARIABLE(var_result, MachineRepresentation::kTagged);
                var_result.Bind(CallBuiltin(Builtins::kStoreInArrayLiteralIC, context, array,
                    index, value, smi_slot, feedback_vector));
                // To avoid special logic in the deoptimizer to re-materialize the value in
                // the accumulator, we overwrite the accumulator after the IC call. It
                // doesn't really matter what we write to the accumulator here, since we
                // restore to the correct value on the outside. Storing the result means we
                // don't need to keep unnecessary state alive across the callstub.
                SetAccumulator(var_result.value());
                Dispatch();
            }

            // StaDataPropertyInLiteral <object> <name> <flags>
            //
            // Define a property <name> with value from the accumulator in <object>.
            // Property attributes and whether set_function_name are stored in
            // DataPropertyInLiteralFlags <flags>.
            //
            // This definition is not observable and is used only for definitions
            // in object or class literals.
            IGNITION_HANDLER(StaDataPropertyInLiteral, InterpreterAssembler)
            {
                Node* object = LoadRegisterAtOperandIndex(0);
                Node* name = LoadRegisterAtOperandIndex(1);
                Node* value = GetAccumulator();
                Node* flags = SmiFromInt32(BytecodeOperandFlag(2));
                Node* vector_index = SmiTag(BytecodeOperandIdx(3));

                Node* feedback_vector = LoadFeedbackVector();
                Node* context = GetContext();

                CallRuntime(Runtime::kDefineDataPropertyInLiteral, context, object, name,
                    value, flags, feedback_vector, vector_index);
                Dispatch();
            }

            IGNITION_HANDLER(CollectTypeProfile, InterpreterAssembler)
            {
                Node* position = BytecodeOperandImmSmi(0);
                Node* value = GetAccumulator();

                Node* feedback_vector = LoadFeedbackVector();
                Node* context = GetContext();

                CallRuntime(Runtime::kCollectTypeProfile, context, position, value,
                    feedback_vector);
                Dispatch();
            }

            // LdaModuleVariable <cell_index> <depth>
            //
            // Load the contents of a module variable into the accumulator.  The variable is
            // identified by <cell_index>.  <depth> is the depth of the current context
            // relative to the module context.
            IGNITION_HANDLER(LdaModuleVariable, InterpreterAssembler)
            {
                Node* cell_index = BytecodeOperandImmIntPtr(0);
                Node* depth = BytecodeOperandUImm(1);

                Node* module_context = GetContextAtDepth(GetContext(), depth);
                Node* module = LoadContextElement(module_context, Context::EXTENSION_INDEX);

                Label if_export(this), if_import(this), end(this);
                Branch(IntPtrGreaterThan(cell_index, IntPtrConstant(0)), &if_export,
                    &if_import);

                BIND(&if_export);
                {
                    TNode<FixedArray> regular_exports = CAST(LoadObjectField(module, Module::kRegularExportsOffset));
                    // The actual array index is (cell_index - 1).
                    Node* export_index = IntPtrSub(cell_index, IntPtrConstant(1));
                    Node* cell = LoadFixedArrayElement(regular_exports, export_index);
                    SetAccumulator(LoadObjectField(cell, Cell::kValueOffset));
                    Goto(&end);
                }

                BIND(&if_import);
                {
                    TNode<FixedArray> regular_imports = CAST(LoadObjectField(module, Module::kRegularImportsOffset));
                    // The actual array index is (-cell_index - 1).
                    Node* import_index = IntPtrSub(IntPtrConstant(-1), cell_index);
                    Node* cell = LoadFixedArrayElement(regular_imports, import_index);
                    SetAccumulator(LoadObjectField(cell, Cell::kValueOffset));
                    Goto(&end);
                }

                BIND(&end);
                Dispatch();
            }

            // StaModuleVariable <cell_index> <depth>
            //
            // Store accumulator to the module variable identified by <cell_index>.
            // <depth> is the depth of the current context relative to the module context.
            IGNITION_HANDLER(StaModuleVariable, InterpreterAssembler)
            {
                Node* value = GetAccumulator();
                Node* cell_index = BytecodeOperandImmIntPtr(0);
                Node* depth = BytecodeOperandUImm(1);

                Node* module_context = GetContextAtDepth(GetContext(), depth);
                Node* module = LoadContextElement(module_context, Context::EXTENSION_INDEX);

                Label if_export(this), if_import(this), end(this);
                Branch(IntPtrGreaterThan(cell_index, IntPtrConstant(0)), &if_export,
                    &if_import);

                BIND(&if_export);
                {
                    TNode<FixedArray> regular_exports = CAST(LoadObjectField(module, Module::kRegularExportsOffset));
                    // The actual array index is (cell_index - 1).
                    Node* export_index = IntPtrSub(cell_index, IntPtrConstant(1));
                    Node* cell = LoadFixedArrayElement(regular_exports, export_index);
                    StoreObjectField(cell, Cell::kValueOffset, value);
                    Goto(&end);
                }

                BIND(&if_import);
                {
                    // Not supported (probably never).
                    Abort(AbortReason::kUnsupportedModuleOperation);
                    Goto(&end);
                }

                BIND(&end);
                Dispatch();
            }

            // PushContext <context>
            //
            // Saves the current context in <context>, and pushes the accumulator as the
            // new current context.
            IGNITION_HANDLER(PushContext, InterpreterAssembler)
            {
                Node* new_context = GetAccumulator();
                Node* old_context = GetContext();
                StoreRegisterAtOperandIndex(old_context, 0);
                SetContext(new_context);
                Dispatch();
            }

            // PopContext <context>
            //
            // Pops the current context and sets <context> as the new context.
            IGNITION_HANDLER(PopContext, InterpreterAssembler)
            {
                Node* context = LoadRegisterAtOperandIndex(0);
                SetContext(context);
                Dispatch();
            }

            class InterpreterBinaryOpAssembler : public InterpreterAssembler {
            public:
                InterpreterBinaryOpAssembler(CodeAssemblerState* state, Bytecode bytecode,
                    OperandScale operand_scale)
                    : InterpreterAssembler(state, bytecode, operand_scale)
                {
                }

                using BinaryOpGenerator = Node* (BinaryOpAssembler::*)(Node* context, Node* left, Node* right,
                    Node* slot, Node* vector, bool lhs_is_smi);

                void BinaryOpWithFeedback(BinaryOpGenerator generator)
                {
                    Node* lhs = LoadRegisterAtOperandIndex(0);
                    Node* rhs = GetAccumulator();
                    Node* context = GetContext();
                    Node* slot_index = BytecodeOperandIdx(1);
                    Node* maybe_feedback_vector = LoadFeedbackVector();

                    BinaryOpAssembler binop_asm(state());
                    Node* result = (binop_asm.*generator)(context, lhs, rhs, slot_index,
                        maybe_feedback_vector, false);
                    SetAccumulator(result);
                    Dispatch();
                }

                void BinaryOpSmiWithFeedback(BinaryOpGenerator generator)
                {
                    Node* lhs = GetAccumulator();
                    Node* rhs = BytecodeOperandImmSmi(0);
                    Node* context = GetContext();
                    Node* slot_index = BytecodeOperandIdx(1);
                    Node* maybe_feedback_vector = LoadFeedbackVector();

                    BinaryOpAssembler binop_asm(state());
                    Node* result = (binop_asm.*generator)(context, lhs, rhs, slot_index,
                        maybe_feedback_vector, true);
                    SetAccumulator(result);
                    Dispatch();
                }
            };

            // Add <src>
            //
            // Add register <src> to accumulator.
            IGNITION_HANDLER(Add, InterpreterBinaryOpAssembler)
            {
                BinaryOpWithFeedback(&BinaryOpAssembler::Generate_AddWithFeedback);
            }

            // Sub <src>
            //
            // Subtract register <src> from accumulator.
            IGNITION_HANDLER(Sub, InterpreterBinaryOpAssembler)
            {
                BinaryOpWithFeedback(&BinaryOpAssembler::Generate_SubtractWithFeedback);
            }

            // Mul <src>
            //
            // Multiply accumulator by register <src>.
            IGNITION_HANDLER(Mul, InterpreterBinaryOpAssembler)
            {
                BinaryOpWithFeedback(&BinaryOpAssembler::Generate_MultiplyWithFeedback);
            }

            // Div <src>
            //
            // Divide register <src> by accumulator.
            IGNITION_HANDLER(Div, InterpreterBinaryOpAssembler)
            {
                BinaryOpWithFeedback(&BinaryOpAssembler::Generate_DivideWithFeedback);
            }

            // Mod <src>
            //
            // Modulo register <src> by accumulator.
            IGNITION_HANDLER(Mod, InterpreterBinaryOpAssembler)
            {
                BinaryOpWithFeedback(&BinaryOpAssembler::Generate_ModulusWithFeedback);
            }

            // Exp <src>
            //
            // Exponentiate register <src> (base) with accumulator (exponent).
            IGNITION_HANDLER(Exp, InterpreterBinaryOpAssembler)
            {
                BinaryOpWithFeedback(&BinaryOpAssembler::Generate_ExponentiateWithFeedback);
            }

            // AddSmi <imm>
            //
            // Adds an immediate value <imm> to the value in the accumulator.
            IGNITION_HANDLER(AddSmi, InterpreterBinaryOpAssembler)
            {
                BinaryOpSmiWithFeedback(&BinaryOpAssembler::Generate_AddWithFeedback);
            }

            // SubSmi <imm>
            //
            // Subtracts an immediate value <imm> from the value in the accumulator.
            IGNITION_HANDLER(SubSmi, InterpreterBinaryOpAssembler)
            {
                BinaryOpSmiWithFeedback(&BinaryOpAssembler::Generate_SubtractWithFeedback);
            }

            // MulSmi <imm>
            //
            // Multiplies an immediate value <imm> to the value in the accumulator.
            IGNITION_HANDLER(MulSmi, InterpreterBinaryOpAssembler)
            {
                BinaryOpSmiWithFeedback(&BinaryOpAssembler::Generate_MultiplyWithFeedback);
            }

            // DivSmi <imm>
            //
            // Divides the value in the accumulator by immediate value <imm>.
            IGNITION_HANDLER(DivSmi, InterpreterBinaryOpAssembler)
            {
                BinaryOpSmiWithFeedback(&BinaryOpAssembler::Generate_DivideWithFeedback);
            }

            // ModSmi <imm>
            //
            // Modulo accumulator by immediate value <imm>.
            IGNITION_HANDLER(ModSmi, InterpreterBinaryOpAssembler)
            {
                BinaryOpSmiWithFeedback(&BinaryOpAssembler::Generate_ModulusWithFeedback);
            }

            // ExpSmi <imm>
            //
            // Exponentiate accumulator (base) with immediate value <imm> (exponent).
            IGNITION_HANDLER(ExpSmi, InterpreterBinaryOpAssembler)
            {
                BinaryOpSmiWithFeedback(
                    &BinaryOpAssembler::Generate_ExponentiateWithFeedback);
            }

            class InterpreterBitwiseBinaryOpAssembler : public InterpreterAssembler {
            public:
                InterpreterBitwiseBinaryOpAssembler(CodeAssemblerState* state,
                    Bytecode bytecode,
                    OperandScale operand_scale)
                    : InterpreterAssembler(state, bytecode, operand_scale)
                {
                }

                void BitwiseBinaryOpWithFeedback(Operation bitwise_op)
                {
                    Node* left = LoadRegisterAtOperandIndex(0);
                    Node* right = GetAccumulator();
                    Node* context = GetContext();
                    Node* slot_index = BytecodeOperandIdx(1);
                    Node* maybe_feedback_vector = LoadFeedbackVector();

                    TVARIABLE(Smi, var_left_feedback);
                    TVARIABLE(Smi, var_right_feedback);
                    VARIABLE(var_left_word32, MachineRepresentation::kWord32);
                    VARIABLE(var_right_word32, MachineRepresentation::kWord32);
                    VARIABLE(var_left_bigint, MachineRepresentation::kTagged, left);
                    VARIABLE(var_right_bigint, MachineRepresentation::kTagged);
                    Label if_left_number(this), do_number_op(this);
                    Label if_left_bigint(this), do_bigint_op(this);

                    TaggedToWord32OrBigIntWithFeedback(context, left, &if_left_number,
                        &var_left_word32, &if_left_bigint,
                        &var_left_bigint, &var_left_feedback);
                    BIND(&if_left_number);
                    TaggedToWord32OrBigIntWithFeedback(context, right, &do_number_op,
                        &var_right_word32, &do_bigint_op,
                        &var_right_bigint, &var_right_feedback);
                    BIND(&do_number_op);
                    TNode<Number> result = BitwiseOp(var_left_word32.value(),
                        var_right_word32.value(), bitwise_op);
                    TNode<Smi> result_type = SelectSmiConstant(
                        TaggedIsSmi(result), BinaryOperationFeedback::kSignedSmall,
                        BinaryOperationFeedback::kNumber);
                    TNode<Smi> input_feedback = SmiOr(var_left_feedback.value(), var_right_feedback.value());
                    UpdateFeedback(SmiOr(result_type, input_feedback), maybe_feedback_vector,
                        slot_index);
                    SetAccumulator(result);
                    Dispatch();

                    // BigInt cases.
                    BIND(&if_left_bigint);
                    TaggedToNumericWithFeedback(context, right, &do_bigint_op,
                        &var_right_bigint, &var_right_feedback);

                    BIND(&do_bigint_op);
                    SetAccumulator(
                        CallRuntime(Runtime::kBigIntBinaryOp, context, var_left_bigint.value(),
                            var_right_bigint.value(), SmiConstant(bitwise_op)));
                    UpdateFeedback(SmiOr(var_left_feedback.value(), var_right_feedback.value()),
                        maybe_feedback_vector, slot_index);
                    Dispatch();
                }

                void BitwiseBinaryOpWithSmi(Operation bitwise_op)
                {
                    Node* left = GetAccumulator();
                    Node* right = BytecodeOperandImmSmi(0);
                    Node* slot_index = BytecodeOperandIdx(1);
                    Node* maybe_feedback_vector = LoadFeedbackVector();
                    Node* context = GetContext();

                    TVARIABLE(Smi, var_left_feedback);
                    VARIABLE(var_left_word32, MachineRepresentation::kWord32);
                    VARIABLE(var_left_bigint, MachineRepresentation::kTagged);
                    Label do_smi_op(this), if_bigint_mix(this);

                    TaggedToWord32OrBigIntWithFeedback(context, left, &do_smi_op,
                        &var_left_word32, &if_bigint_mix,
                        &var_left_bigint, &var_left_feedback);
                    BIND(&do_smi_op);
                    TNode<Number> result = BitwiseOp(var_left_word32.value(), SmiToInt32(right), bitwise_op);
                    TNode<Smi> result_type = SelectSmiConstant(
                        TaggedIsSmi(result), BinaryOperationFeedback::kSignedSmall,
                        BinaryOperationFeedback::kNumber);
                    UpdateFeedback(SmiOr(result_type, var_left_feedback.value()),
                        maybe_feedback_vector, slot_index);
                    SetAccumulator(result);
                    Dispatch();

                    BIND(&if_bigint_mix);
                    UpdateFeedback(var_left_feedback.value(), maybe_feedback_vector,
                        slot_index);
                    ThrowTypeError(context, MessageTemplate::kBigIntMixedTypes);
                }
            };

            // BitwiseOr <src>
            //
            // BitwiseOr register <src> to accumulator.
            IGNITION_HANDLER(BitwiseOr, InterpreterBitwiseBinaryOpAssembler)
            {
                BitwiseBinaryOpWithFeedback(Operation::kBitwiseOr);
            }

            // BitwiseXor <src>
            //
            // BitwiseXor register <src> to accumulator.
            IGNITION_HANDLER(BitwiseXor, InterpreterBitwiseBinaryOpAssembler)
            {
                BitwiseBinaryOpWithFeedback(Operation::kBitwiseXor);
            }

            // BitwiseAnd <src>
            //
            // BitwiseAnd register <src> to accumulator.
            IGNITION_HANDLER(BitwiseAnd, InterpreterBitwiseBinaryOpAssembler)
            {
                BitwiseBinaryOpWithFeedback(Operation::kBitwiseAnd);
            }

            // ShiftLeft <src>
            //
            // Left shifts register <src> by the count specified in the accumulator.
            // Register <src> is converted to an int32 and the accumulator to uint32
            // before the operation. 5 lsb bits from the accumulator are used as count
            // i.e. <src> << (accumulator & 0x1F).
            IGNITION_HANDLER(ShiftLeft, InterpreterBitwiseBinaryOpAssembler)
            {
                BitwiseBinaryOpWithFeedback(Operation::kShiftLeft);
            }

            // ShiftRight <src>
            //
            // Right shifts register <src> by the count specified in the accumulator.
            // Result is sign extended. Register <src> is converted to an int32 and the
            // accumulator to uint32 before the operation. 5 lsb bits from the accumulator
            // are used as count i.e. <src> >> (accumulator & 0x1F).
            IGNITION_HANDLER(ShiftRight, InterpreterBitwiseBinaryOpAssembler)
            {
                BitwiseBinaryOpWithFeedback(Operation::kShiftRight);
            }

            // ShiftRightLogical <src>
            //
            // Right Shifts register <src> by the count specified in the accumulator.
            // Result is zero-filled. The accumulator and register <src> are converted to
            // uint32 before the operation 5 lsb bits from the accumulator are used as
            // count i.e. <src> << (accumulator & 0x1F).
            IGNITION_HANDLER(ShiftRightLogical, InterpreterBitwiseBinaryOpAssembler)
            {
                BitwiseBinaryOpWithFeedback(Operation::kShiftRightLogical);
            }

            // BitwiseOrSmi <imm>
            //
            // BitwiseOrSmi accumulator with <imm>.
            IGNITION_HANDLER(BitwiseOrSmi, InterpreterBitwiseBinaryOpAssembler)
            {
                BitwiseBinaryOpWithSmi(Operation::kBitwiseOr);
            }

            // BitwiseXorSmi <imm>
            //
            // BitwiseXorSmi accumulator with <imm>.
            IGNITION_HANDLER(BitwiseXorSmi, InterpreterBitwiseBinaryOpAssembler)
            {
                BitwiseBinaryOpWithSmi(Operation::kBitwiseXor);
            }

            // BitwiseAndSmi <imm>
            //
            // BitwiseAndSmi accumulator with <imm>.
            IGNITION_HANDLER(BitwiseAndSmi, InterpreterBitwiseBinaryOpAssembler)
            {
                BitwiseBinaryOpWithSmi(Operation::kBitwiseAnd);
            }

            // BitwiseNot <feedback_slot>
            //
            // Perform bitwise-not on the accumulator.
            IGNITION_HANDLER(BitwiseNot, InterpreterAssembler)
            {
                Node* operand = GetAccumulator();
                Node* slot_index = BytecodeOperandIdx(0);
                Node* maybe_feedback_vector = LoadFeedbackVector();
                Node* context = GetContext();

                VARIABLE(var_word32, MachineRepresentation::kWord32);
                TVARIABLE(Smi, var_feedback);
                VARIABLE(var_bigint, MachineRepresentation::kTagged);
                Label if_number(this), if_bigint(this);
                TaggedToWord32OrBigIntWithFeedback(context, operand, &if_number, &var_word32,
                    &if_bigint, &var_bigint, &var_feedback);

                // Number case.
                BIND(&if_number);
                TNode<Number> result = ChangeInt32ToTagged(Signed(Word32BitwiseNot(var_word32.value())));
                TNode<Smi> result_type = SelectSmiConstant(
                    TaggedIsSmi(result), BinaryOperationFeedback::kSignedSmall,
                    BinaryOperationFeedback::kNumber);
                UpdateFeedback(SmiOr(result_type, var_feedback.value()),
                    maybe_feedback_vector, slot_index);
                SetAccumulator(result);
                Dispatch();

                // BigInt case.
                BIND(&if_bigint);
                UpdateFeedback(SmiConstant(BinaryOperationFeedback::kBigInt),
                    maybe_feedback_vector, slot_index);
                SetAccumulator(CallRuntime(Runtime::kBigIntUnaryOp, context,
                    var_bigint.value(),
                    SmiConstant(Operation::kBitwiseNot)));
                Dispatch();
            }

            // ShiftLeftSmi <imm>
            //
            // Left shifts accumulator by the count specified in <imm>.
            // The accumulator is converted to an int32 before the operation. The 5
            // lsb bits from <imm> are used as count i.e. <src> << (<imm> & 0x1F).
            IGNITION_HANDLER(ShiftLeftSmi, InterpreterBitwiseBinaryOpAssembler)
            {
                BitwiseBinaryOpWithSmi(Operation::kShiftLeft);
            }

            // ShiftRightSmi <imm>
            //
            // Right shifts accumulator by the count specified in <imm>. Result is sign
            // extended. The accumulator is converted to an int32 before the operation. The
            // 5 lsb bits from <imm> are used as count i.e. <src> >> (<imm> & 0x1F).
            IGNITION_HANDLER(ShiftRightSmi, InterpreterBitwiseBinaryOpAssembler)
            {
                BitwiseBinaryOpWithSmi(Operation::kShiftRight);
            }

            // ShiftRightLogicalSmi <imm>
            //
            // Right shifts accumulator by the count specified in <imm>. Result is zero
            // extended. The accumulator is converted to an int32 before the operation. The
            // 5 lsb bits from <imm> are used as count i.e. <src> >>> (<imm> & 0x1F).
            IGNITION_HANDLER(ShiftRightLogicalSmi, InterpreterBitwiseBinaryOpAssembler)
            {
                BitwiseBinaryOpWithSmi(Operation::kShiftRightLogical);
            }

            class UnaryNumericOpAssembler : public InterpreterAssembler {
            public:
                UnaryNumericOpAssembler(CodeAssemblerState* state, Bytecode bytecode,
                    OperandScale operand_scale)
                    : InterpreterAssembler(state, bytecode, operand_scale)
                {
                }

                virtual ~UnaryNumericOpAssembler() = default;

                // Must return a tagged value.
                virtual TNode<Number> SmiOp(TNode<Smi> smi_value, Variable* var_feedback,
                    Label* do_float_op, Variable* var_float)
                    = 0;
                // Must return a Float64 value.
                virtual Node* FloatOp(Node* float_value) = 0;
                // Must return a tagged value.
                virtual Node* BigIntOp(Node* bigint_value) = 0;

                void UnaryOpWithFeedback()
                {
                    VARIABLE(var_value, MachineRepresentation::kTagged, GetAccumulator());
                    VARIABLE(var_result, MachineRepresentation::kTagged);
                    VARIABLE(var_float_value, MachineRepresentation::kFloat64);
                    TVARIABLE(Smi, var_feedback, SmiConstant(BinaryOperationFeedback::kNone));
                    Variable* loop_vars[] = { &var_value, &var_feedback };
                    Label start(this, arraysize(loop_vars), loop_vars), end(this);
                    Label do_float_op(this, &var_float_value);
                    Goto(&start);
                    // We might have to try again after ToNumeric conversion.
                    BIND(&start);
                    {
                        Label if_smi(this), if_heapnumber(this), if_oddball(this);
                        Label if_bigint(this, Label::kDeferred);
                        Label if_other(this, Label::kDeferred);
                        Node* value = var_value.value();
                        GotoIf(TaggedIsSmi(value), &if_smi);
                        Node* map = LoadMap(value);
                        GotoIf(IsHeapNumberMap(map), &if_heapnumber);
                        Node* instance_type = LoadMapInstanceType(map);
                        GotoIf(IsBigIntInstanceType(instance_type), &if_bigint);
                        Branch(InstanceTypeEqual(instance_type, ODDBALL_TYPE), &if_oddball,
                            &if_other);

                        BIND(&if_smi);
                        {
                            var_result.Bind(
                                SmiOp(CAST(value), &var_feedback, &do_float_op, &var_float_value));
                            Goto(&end);
                        }

                        BIND(&if_heapnumber);
                        {
                            var_float_value.Bind(LoadHeapNumberValue(value));
                            Goto(&do_float_op);
                        }

                        BIND(&if_bigint);
                        {
                            var_result.Bind(BigIntOp(value));
                            CombineFeedback(&var_feedback, BinaryOperationFeedback::kBigInt);
                            Goto(&end);
                        }

                        BIND(&if_oddball);
                        {
                            // We do not require an Or with earlier feedback here because once we
                            // convert the value to a number, we cannot reach this path. We can
                            // only reach this path on the first pass when the feedback is kNone.
                            CSA_ASSERT(this, SmiEqual(var_feedback.value(), SmiConstant(BinaryOperationFeedback::kNone)));
                            OverwriteFeedback(&var_feedback,
                                BinaryOperationFeedback::kNumberOrOddball);
                            var_value.Bind(LoadObjectField(value, Oddball::kToNumberOffset));
                            Goto(&start);
                        }

                        BIND(&if_other);
                        {
                            // We do not require an Or with earlier feedback here because once we
                            // convert the value to a number, we cannot reach this path. We can
                            // only reach this path on the first pass when the feedback is kNone.
                            CSA_ASSERT(this, SmiEqual(var_feedback.value(), SmiConstant(BinaryOperationFeedback::kNone)));
                            OverwriteFeedback(&var_feedback, BinaryOperationFeedback::kAny);
                            var_value.Bind(
                                CallBuiltin(Builtins::kNonNumberToNumeric, GetContext(), value));
                            Goto(&start);
                        }
                    }

                    BIND(&do_float_op);
                    {
                        CombineFeedback(&var_feedback, BinaryOperationFeedback::kNumber);
                        var_result.Bind(
                            AllocateHeapNumberWithValue(FloatOp(var_float_value.value())));
                        Goto(&end);
                    }

                    BIND(&end);
                    Node* slot_index = BytecodeOperandIdx(0);
                    Node* maybe_feedback_vector = LoadFeedbackVector();
                    UpdateFeedback(var_feedback.value(), maybe_feedback_vector, slot_index);
                    SetAccumulator(var_result.value());
                    Dispatch();
                }
            };

            class NegateAssemblerImpl : public UnaryNumericOpAssembler {
            public:
                explicit NegateAssemblerImpl(CodeAssemblerState* state, Bytecode bytecode,
                    OperandScale operand_scale)
                    : UnaryNumericOpAssembler(state, bytecode, operand_scale)
                {
                }

                TNode<Number> SmiOp(TNode<Smi> smi_value, Variable* var_feedback,
                    Label* do_float_op, Variable* var_float) override
                {
                    TVARIABLE(Number, var_result);
                    Label if_zero(this), if_min_smi(this), end(this);
                    // Return -0 if operand is 0.
                    GotoIf(SmiEqual(smi_value, SmiConstant(0)), &if_zero);

                    // Special-case the minimum Smi to avoid overflow.
                    GotoIf(SmiEqual(smi_value, SmiConstant(Smi::kMinValue)), &if_min_smi);

                    // Else simply subtract operand from 0.
                    CombineFeedback(var_feedback, BinaryOperationFeedback::kSignedSmall);
                    var_result = SmiSub(SmiConstant(0), smi_value);
                    Goto(&end);

                    BIND(&if_zero);
                    CombineFeedback(var_feedback, BinaryOperationFeedback::kNumber);
                    var_result = MinusZeroConstant();
                    Goto(&end);

                    BIND(&if_min_smi);
                    var_float->Bind(SmiToFloat64(smi_value));
                    Goto(do_float_op);

                    BIND(&end);
                    return var_result.value();
                }

                Node* FloatOp(Node* float_value) override { return Float64Neg(float_value); }

                Node* BigIntOp(Node* bigint_value) override
                {
                    return CallRuntime(Runtime::kBigIntUnaryOp, GetContext(), bigint_value,
                        SmiConstant(Operation::kNegate));
                }
            };

            // Negate <feedback_slot>
            //
            // Perform arithmetic negation on the accumulator.
            IGNITION_HANDLER(Negate, NegateAssemblerImpl) { UnaryOpWithFeedback(); }

            // ToName <dst>
            //
            // Convert the object referenced by the accumulator to a name.
            IGNITION_HANDLER(ToName, InterpreterAssembler)
            {
                Node* object = GetAccumulator();
                Node* context = GetContext();
                Node* result = CallBuiltin(Builtins::kToName, context, object);
                StoreRegisterAtOperandIndex(result, 0);
                Dispatch();
            }

            // ToNumber <slot>
            //
            // Convert the object referenced by the accumulator to a number.
            IGNITION_HANDLER(ToNumber, InterpreterAssembler)
            {
                ToNumberOrNumeric(Object::Conversion::kToNumber);
            }

            // ToNumeric <slot>
            //
            // Convert the object referenced by the accumulator to a numeric.
            IGNITION_HANDLER(ToNumeric, InterpreterAssembler)
            {
                ToNumberOrNumeric(Object::Conversion::kToNumeric);
            }

            // ToObject <dst>
            //
            // Convert the object referenced by the accumulator to a JSReceiver.
            IGNITION_HANDLER(ToObject, InterpreterAssembler)
            {
                Node* accumulator = GetAccumulator();
                Node* context = GetContext();
                Node* result = CallBuiltin(Builtins::kToObject, context, accumulator);
                StoreRegisterAtOperandIndex(result, 0);
                Dispatch();
            }

            // ToString
            //
            // Convert the accumulator to a String.
            IGNITION_HANDLER(ToString, InterpreterAssembler)
            {
                SetAccumulator(ToString_Inline(GetContext(), GetAccumulator()));
                Dispatch();
            }

            class IncDecAssembler : public UnaryNumericOpAssembler {
            public:
                explicit IncDecAssembler(CodeAssemblerState* state, Bytecode bytecode,
                    OperandScale operand_scale)
                    : UnaryNumericOpAssembler(state, bytecode, operand_scale)
                {
                }

                Operation op()
                {
                    DCHECK(op_ == Operation::kIncrement || op_ == Operation::kDecrement);
                    return op_;
                }

                TNode<Number> SmiOp(TNode<Smi> value, Variable* var_feedback,
                    Label* do_float_op, Variable* var_float) override
                {
                    TNode<Smi> one = SmiConstant(1);
                    Label if_overflow(this), if_notoverflow(this);
                    TNode<Smi> result = op() == Operation::kIncrement
                        ? TrySmiAdd(value, one, &if_overflow)
                        : TrySmiSub(value, one, &if_overflow);
                    Goto(&if_notoverflow);

                    BIND(&if_overflow);
                    {
                        var_float->Bind(SmiToFloat64(value));
                        Goto(do_float_op);
                    }

                    BIND(&if_notoverflow);
                    CombineFeedback(var_feedback, BinaryOperationFeedback::kSignedSmall);
                    return result;
                }

                Node* FloatOp(Node* float_value) override
                {
                    return op() == Operation::kIncrement
                        ? Float64Add(float_value, Float64Constant(1.0))
                        : Float64Sub(float_value, Float64Constant(1.0));
                }

                Node* BigIntOp(Node* bigint_value) override
                {
                    return CallRuntime(Runtime::kBigIntUnaryOp, GetContext(), bigint_value,
                        SmiConstant(op()));
                }

                void IncWithFeedback()
                {
                    op_ = Operation::kIncrement;
                    UnaryOpWithFeedback();
                }

                void DecWithFeedback()
                {
                    op_ = Operation::kDecrement;
                    UnaryOpWithFeedback();
                }

            private:
                Operation op_ = Operation::kEqual; // Dummy initialization.
            };

            // Inc
            //
            // Increments value in the accumulator by one.
            IGNITION_HANDLER(Inc, IncDecAssembler) { IncWithFeedback(); }

            // Dec
            //
            // Decrements value in the accumulator by one.
            IGNITION_HANDLER(Dec, IncDecAssembler) { DecWithFeedback(); }

            // ToBooleanLogicalNot
            //
            // Perform logical-not on the accumulator, first casting the
            // accumulator to a boolean value if required.
            IGNITION_HANDLER(ToBooleanLogicalNot, InterpreterAssembler)
            {
                Node* value = GetAccumulator();
                Variable result(this, MachineRepresentation::kTagged);
                Label if_true(this), if_false(this), end(this);
                BranchIfToBooleanIsTrue(value, &if_true, &if_false);
                BIND(&if_true);
                {
                    result.Bind(FalseConstant());
                    Goto(&end);
                }
                BIND(&if_false);
                {
                    result.Bind(TrueConstant());
                    Goto(&end);
                }
                BIND(&end);
                SetAccumulator(result.value());
                Dispatch();
            }

            // LogicalNot
            //
            // Perform logical-not on the accumulator, which must already be a boolean
            // value.
            IGNITION_HANDLER(LogicalNot, InterpreterAssembler)
            {
                Node* value = GetAccumulator();
                Variable result(this, MachineRepresentation::kTagged);
                Label if_true(this), if_false(this), end(this);
                Node* true_value = TrueConstant();
                Node* false_value = FalseConstant();
                Branch(WordEqual(value, true_value), &if_true, &if_false);
                BIND(&if_true);
                {
                    result.Bind(false_value);
                    Goto(&end);
                }
                BIND(&if_false);
                {
                    CSA_ASSERT(this, WordEqual(value, false_value));
                    result.Bind(true_value);
                    Goto(&end);
                }
                BIND(&end);
                SetAccumulator(result.value());
                Dispatch();
            }

            // TypeOf
            //
            // Load the accumulator with the string representating type of the
            // object in the accumulator.
            IGNITION_HANDLER(TypeOf, InterpreterAssembler)
            {
                Node* value = GetAccumulator();
                Node* result = Typeof(value);
                SetAccumulator(result);
                Dispatch();
            }

            // DeletePropertyStrict
            //
            // Delete the property specified in the accumulator from the object
            // referenced by the register operand following strict mode semantics.
            IGNITION_HANDLER(DeletePropertyStrict, InterpreterAssembler)
            {
                Node* object = LoadRegisterAtOperandIndex(0);
                Node* key = GetAccumulator();
                Node* context = GetContext();
                Node* result = CallBuiltin(Builtins::kDeleteProperty, context, object, key,
                    SmiConstant(Smi::FromEnum(LanguageMode::kStrict)));
                SetAccumulator(result);
                Dispatch();
            }

            // DeletePropertySloppy
            //
            // Delete the property specified in the accumulator from the object
            // referenced by the register operand following sloppy mode semantics.
            IGNITION_HANDLER(DeletePropertySloppy, InterpreterAssembler)
            {
                Node* object = LoadRegisterAtOperandIndex(0);
                Node* key = GetAccumulator();
                Node* context = GetContext();
                Node* result = CallBuiltin(Builtins::kDeleteProperty, context, object, key,
                    SmiConstant(Smi::FromEnum(LanguageMode::kSloppy)));
                SetAccumulator(result);
                Dispatch();
            }

            // GetSuperConstructor
            //
            // Get the super constructor from the object referenced by the accumulator.
            // The result is stored in register |reg|.
            IGNITION_HANDLER(GetSuperConstructor, InterpreterAssembler)
            {
                Node* active_function = GetAccumulator();
                Node* context = GetContext();
                Node* result = GetSuperConstructor(context, active_function);
                StoreRegisterAtOperandIndex(result, 0);
                Dispatch();
            }

            class InterpreterJSCallAssembler : public InterpreterAssembler {
            public:
                InterpreterJSCallAssembler(CodeAssemblerState* state, Bytecode bytecode,
                    OperandScale operand_scale)
                    : InterpreterAssembler(state, bytecode, operand_scale)
                {
                }

                // Generates code to perform a JS call that collects type feedback.
                void JSCall(ConvertReceiverMode receiver_mode)
                {
                    Node* function = LoadRegisterAtOperandIndex(0);
                    RegListNodePair args = GetRegisterListAtOperandIndex(1);
                    Node* slot_id = BytecodeOperandIdx(3);
                    Node* maybe_feedback_vector = LoadFeedbackVector();
                    Node* context = GetContext();

                    // Collect the {function} feedback.
                    CollectCallFeedback(function, context, maybe_feedback_vector, slot_id);

                    // Call the function and dispatch to the next handler.
                    CallJSAndDispatch(function, context, args, receiver_mode);
                }

                // Generates code to perform a JS call without collecting feedback.
                void JSCallNoFeedback(ConvertReceiverMode receiver_mode)
                {
                    Node* function = LoadRegisterAtOperandIndex(0);
                    RegListNodePair args = GetRegisterListAtOperandIndex(1);
                    Node* context = GetContext();

                    // Call the function and dispatch to the next handler.
                    CallJSAndDispatch(function, context, args, receiver_mode);
                }

                // Generates code to perform a JS call with a known number of arguments that
                // collects type feedback.
                void JSCallN(int arg_count, ConvertReceiverMode receiver_mode)
                {
                    // Indices and counts of operands on the bytecode.
                    const int kFirstArgumentOperandIndex = 1;
                    const int kReceiverOperandCount = (receiver_mode == ConvertReceiverMode::kNullOrUndefined) ? 0 : 1;
                    const int kRecieverAndArgOperandCount = kReceiverOperandCount + arg_count;
                    const int kSlotOperandIndex = kFirstArgumentOperandIndex + kRecieverAndArgOperandCount;

                    Node* function = LoadRegisterAtOperandIndex(0);
                    Node* slot_id = BytecodeOperandIdx(kSlotOperandIndex);
                    Node* maybe_feedback_vector = LoadFeedbackVector();
                    Node* context = GetContext();

                    // Collect the {function} feedback.
                    CollectCallFeedback(function, context, maybe_feedback_vector, slot_id);

                    switch (kRecieverAndArgOperandCount) {
                    case 0:
                        CallJSAndDispatch(function, context, Int32Constant(arg_count),
                            receiver_mode);
                        break;
                    case 1:
                        CallJSAndDispatch(
                            function, context, Int32Constant(arg_count), receiver_mode,
                            LoadRegisterAtOperandIndex(kFirstArgumentOperandIndex));
                        break;
                    case 2:
                        CallJSAndDispatch(
                            function, context, Int32Constant(arg_count), receiver_mode,
                            LoadRegisterAtOperandIndex(kFirstArgumentOperandIndex),
                            LoadRegisterAtOperandIndex(kFirstArgumentOperandIndex + 1));
                        break;
                    case 3:
                        CallJSAndDispatch(
                            function, context, Int32Constant(arg_count), receiver_mode,
                            LoadRegisterAtOperandIndex(kFirstArgumentOperandIndex),
                            LoadRegisterAtOperandIndex(kFirstArgumentOperandIndex + 1),
                            LoadRegisterAtOperandIndex(kFirstArgumentOperandIndex + 2));
                        break;
                    default:
                        UNREACHABLE();
                    }
                }
            };

            // Call <callable> <receiver> <arg_count> <feedback_slot_id>
            //
            // Call a JSfunction or Callable in |callable| with the |receiver| and
            // |arg_count| arguments in subsequent registers. Collect type feedback
            // into |feedback_slot_id|
            IGNITION_HANDLER(CallAnyReceiver, InterpreterJSCallAssembler)
            {
                JSCall(ConvertReceiverMode::kAny);
            }

            IGNITION_HANDLER(CallProperty, InterpreterJSCallAssembler)
            {
                JSCall(ConvertReceiverMode::kNotNullOrUndefined);
            }

            IGNITION_HANDLER(CallProperty0, InterpreterJSCallAssembler)
            {
                JSCallN(0, ConvertReceiverMode::kNotNullOrUndefined);
            }

            IGNITION_HANDLER(CallProperty1, InterpreterJSCallAssembler)
            {
                JSCallN(1, ConvertReceiverMode::kNotNullOrUndefined);
            }

            IGNITION_HANDLER(CallProperty2, InterpreterJSCallAssembler)
            {
                JSCallN(2, ConvertReceiverMode::kNotNullOrUndefined);
            }

            IGNITION_HANDLER(CallUndefinedReceiver, InterpreterJSCallAssembler)
            {
                JSCall(ConvertReceiverMode::kNullOrUndefined);
            }

            IGNITION_HANDLER(CallUndefinedReceiver0, InterpreterJSCallAssembler)
            {
                JSCallN(0, ConvertReceiverMode::kNullOrUndefined);
            }

            IGNITION_HANDLER(CallUndefinedReceiver1, InterpreterJSCallAssembler)
            {
                JSCallN(1, ConvertReceiverMode::kNullOrUndefined);
            }

            IGNITION_HANDLER(CallUndefinedReceiver2, InterpreterJSCallAssembler)
            {
                JSCallN(2, ConvertReceiverMode::kNullOrUndefined);
            }

            IGNITION_HANDLER(CallNoFeedback, InterpreterJSCallAssembler)
            {
                JSCallNoFeedback(ConvertReceiverMode::kAny);
            }

            // CallRuntime <function_id> <first_arg> <arg_count>
            //
            // Call the runtime function |function_id| with the first argument in
            // register |first_arg| and |arg_count| arguments in subsequent
            // registers.
            IGNITION_HANDLER(CallRuntime, InterpreterAssembler)
            {
                Node* function_id = BytecodeOperandRuntimeId(0);
                RegListNodePair args = GetRegisterListAtOperandIndex(1);
                Node* context = GetContext();
                Node* result = CallRuntimeN(function_id, context, args);
                SetAccumulator(result);
                Dispatch();
            }

            // InvokeIntrinsic <function_id> <first_arg> <arg_count>
            //
            // Implements the semantic equivalent of calling the runtime function
            // |function_id| with the first argument in |first_arg| and |arg_count|
            // arguments in subsequent registers.
            IGNITION_HANDLER(InvokeIntrinsic, InterpreterAssembler)
            {
                Node* function_id = BytecodeOperandIntrinsicId(0);
                RegListNodePair args = GetRegisterListAtOperandIndex(1);
                Node* context = GetContext();
                Node* result = GenerateInvokeIntrinsic(this, function_id, context, args);
                SetAccumulator(result);
                Dispatch();
            }

            // CallRuntimeForPair <function_id> <first_arg> <arg_count> <first_return>
            //
            // Call the runtime function |function_id| which returns a pair, with the
            // first argument in register |first_arg| and |arg_count| arguments in
            // subsequent registers. Returns the result in <first_return> and
            // <first_return + 1>
            IGNITION_HANDLER(CallRuntimeForPair, InterpreterAssembler)
            {
                // Call the runtime function.
                Node* function_id = BytecodeOperandRuntimeId(0);
                RegListNodePair args = GetRegisterListAtOperandIndex(1);
                Node* context = GetContext();
                Node* result_pair = CallRuntimeN(function_id, context, args, 2);
                // Store the results in <first_return> and <first_return + 1>
                Node* result0 = Projection(0, result_pair);
                Node* result1 = Projection(1, result_pair);
                StoreRegisterPairAtOperandIndex(result0, result1, 3);
                Dispatch();
            }

            // CallJSRuntime <context_index> <receiver> <arg_count>
            //
            // Call the JS runtime function that has the |context_index| with the receiver
            // in register |receiver| and |arg_count| arguments in subsequent registers.
            IGNITION_HANDLER(CallJSRuntime, InterpreterAssembler)
            {
                Node* context_index = BytecodeOperandNativeContextIndex(0);
                RegListNodePair args = GetRegisterListAtOperandIndex(1);

                // Get the function to call from the native context.
                Node* context = GetContext();
                Node* native_context = LoadNativeContext(context);
                Node* function = LoadContextElement(native_context, context_index);

                // Call the function.
                CallJSAndDispatch(function, context, args,
                    ConvertReceiverMode::kNullOrUndefined);
            }

            // CallWithSpread <callable> <first_arg> <arg_count>
            //
            // Call a JSfunction or Callable in |callable| with the receiver in
            // |first_arg| and |arg_count - 1| arguments in subsequent registers. The
            // final argument is always a spread.
            //
            IGNITION_HANDLER(CallWithSpread, InterpreterAssembler)
            {
                Node* callable = LoadRegisterAtOperandIndex(0);
                RegListNodePair args = GetRegisterListAtOperandIndex(1);
                Node* slot_id = BytecodeOperandIdx(3);
                Node* maybe_feedback_vector = LoadFeedbackVector();
                Node* context = GetContext();

                // Call into Runtime function CallWithSpread which does everything.
                CallJSWithSpreadAndDispatch(callable, context, args, slot_id,
                    maybe_feedback_vector);
            }

            // ConstructWithSpread <first_arg> <arg_count>
            //
            // Call the constructor in |constructor| with the first argument in register
            // |first_arg| and |arg_count| arguments in subsequent registers. The final
            // argument is always a spread. The new.target is in the accumulator.
            //
            IGNITION_HANDLER(ConstructWithSpread, InterpreterAssembler)
            {
                Node* new_target = GetAccumulator();
                Node* constructor = LoadRegisterAtOperandIndex(0);
                RegListNodePair args = GetRegisterListAtOperandIndex(1);
                Node* slot_id = BytecodeOperandIdx(3);
                Node* feedback_vector = LoadFeedbackVector();
                Node* context = GetContext();
                Node* result = ConstructWithSpread(constructor, context, new_target, args,
                    slot_id, feedback_vector);
                SetAccumulator(result);
                Dispatch();
            }

            // Construct <constructor> <first_arg> <arg_count>
            //
            // Call operator construct with |constructor| and the first argument in
            // register |first_arg| and |arg_count| arguments in subsequent
            // registers. The new.target is in the accumulator.
            //
            IGNITION_HANDLER(Construct, InterpreterAssembler)
            {
                Node* new_target = GetAccumulator();
                Node* constructor = LoadRegisterAtOperandIndex(0);
                RegListNodePair args = GetRegisterListAtOperandIndex(1);
                Node* slot_id = BytecodeOperandIdx(3);
                Node* feedback_vector = LoadFeedbackVector();
                Node* context = GetContext();
                Node* result = Construct(constructor, context, new_target, args, slot_id,
                    feedback_vector);
                SetAccumulator(result);
                Dispatch();
            }

            class InterpreterCompareOpAssembler : public InterpreterAssembler {
            public:
                InterpreterCompareOpAssembler(CodeAssemblerState* state, Bytecode bytecode,
                    OperandScale operand_scale)
                    : InterpreterAssembler(state, bytecode, operand_scale)
                {
                }

                void CompareOpWithFeedback(Operation compare_op)
                {
                    Node* lhs = LoadRegisterAtOperandIndex(0);
                    Node* rhs = GetAccumulator();
                    Node* context = GetContext();

                    Variable var_type_feedback(this, MachineRepresentation::kTagged);
                    Node* result;
                    switch (compare_op) {
                    case Operation::kEqual:
                        result = Equal(lhs, rhs, context, &var_type_feedback);
                        break;
                    case Operation::kStrictEqual:
                        result = StrictEqual(lhs, rhs, &var_type_feedback);
                        break;
                    case Operation::kLessThan:
                    case Operation::kGreaterThan:
                    case Operation::kLessThanOrEqual:
                    case Operation::kGreaterThanOrEqual:
                        result = RelationalComparison(compare_op, lhs, rhs, context,
                            &var_type_feedback);
                        break;
                    default:
                        UNREACHABLE();
                    }

                    Node* slot_index = BytecodeOperandIdx(1);
                    Node* maybe_feedback_vector = LoadFeedbackVector();
                    UpdateFeedback(var_type_feedback.value(), maybe_feedback_vector,
                        slot_index);
                    SetAccumulator(result);
                    Dispatch();
                }
            };

            // TestEqual <src>
            //
            // Test if the value in the <src> register equals the accumulator.
            IGNITION_HANDLER(TestEqual, InterpreterCompareOpAssembler)
            {
                CompareOpWithFeedback(Operation::kEqual);
            }

            // TestEqualStrict <src>
            //
            // Test if the value in the <src> register is strictly equal to the accumulator.
            IGNITION_HANDLER(TestEqualStrict, InterpreterCompareOpAssembler)
            {
                CompareOpWithFeedback(Operation::kStrictEqual);
            }

            // TestLessThan <src>
            //
            // Test if the value in the <src> register is less than the accumulator.
            IGNITION_HANDLER(TestLessThan, InterpreterCompareOpAssembler)
            {
                CompareOpWithFeedback(Operation::kLessThan);
            }

            // TestGreaterThan <src>
            //
            // Test if the value in the <src> register is greater than the accumulator.
            IGNITION_HANDLER(TestGreaterThan, InterpreterCompareOpAssembler)
            {
                CompareOpWithFeedback(Operation::kGreaterThan);
            }

            // TestLessThanOrEqual <src>
            //
            // Test if the value in the <src> register is less than or equal to the
            // accumulator.
            IGNITION_HANDLER(TestLessThanOrEqual, InterpreterCompareOpAssembler)
            {
                CompareOpWithFeedback(Operation::kLessThanOrEqual);
            }

            // TestGreaterThanOrEqual <src>
            //
            // Test if the value in the <src> register is greater than or equal to the
            // accumulator.
            IGNITION_HANDLER(TestGreaterThanOrEqual, InterpreterCompareOpAssembler)
            {
                CompareOpWithFeedback(Operation::kGreaterThanOrEqual);
            }

            // TestReferenceEqual <src>
            //
            // Test if the value in the <src> register is equal to the accumulator
            // by means of simple comparison. For SMIs and simple reference comparisons.
            IGNITION_HANDLER(TestReferenceEqual, InterpreterAssembler)
            {
                Node* lhs = LoadRegisterAtOperandIndex(0);
                Node* rhs = GetAccumulator();
                Node* result = SelectBooleanConstant(WordEqual(lhs, rhs));
                SetAccumulator(result);
                Dispatch();
            }

            // TestIn <src> <feedback_slot>
            //
            // Test if the object referenced by the register operand is a property of the
            // object referenced by the accumulator.
            IGNITION_HANDLER(TestIn, InterpreterAssembler)
            {
                Node* name = LoadRegisterAtOperandIndex(0);
                Node* object = GetAccumulator();
                Node* raw_slot = BytecodeOperandIdx(1);
                Node* smi_slot = SmiTag(raw_slot);
                Node* feedback_vector = LoadFeedbackVector();
                Node* context = GetContext();

                VARIABLE(var_result, MachineRepresentation::kTagged);
                var_result.Bind(CallBuiltin(Builtins::kKeyedHasIC, context, object, name,
                    smi_slot, feedback_vector));
                SetAccumulator(var_result.value());
                Dispatch();
            }

            // TestInstanceOf <src> <feedback_slot>
            //
            // Test if the object referenced by the <src> register is an an instance of type
            // referenced by the accumulator.
            IGNITION_HANDLER(TestInstanceOf, InterpreterAssembler)
            {
                Node* object = LoadRegisterAtOperandIndex(0);
                Node* callable = GetAccumulator();
                Node* slot_id = BytecodeOperandIdx(1);
                Node* feedback_vector = LoadFeedbackVector();
                Node* context = GetContext();

                Label feedback_done(this);
                GotoIf(IsUndefined(feedback_vector), &feedback_done);

                // Record feedback for the {callable} in the {feedback_vector}.
                CollectCallableFeedback(callable, context, feedback_vector, slot_id);
                Goto(&feedback_done);

                BIND(&feedback_done);
                // Perform the actual instanceof operation.
                SetAccumulator(InstanceOf(object, callable, context));
                Dispatch();
            }

            // TestUndetectable
            //
            // Test if the value in the accumulator is undetectable (null, undefined or
            // document.all).
            IGNITION_HANDLER(TestUndetectable, InterpreterAssembler)
            {
                Label return_false(this), end(this);
                Node* object = GetAccumulator();

                // If the object is an Smi then return false.
                SetAccumulator(FalseConstant());
                GotoIf(TaggedIsSmi(object), &end);

                // If it is a HeapObject, load the map and check for undetectable bit.
                Node* result = SelectBooleanConstant(IsUndetectableMap(LoadMap(object)));
                SetAccumulator(result);
                Goto(&end);

                BIND(&end);
                Dispatch();
            }

            // TestNull
            //
            // Test if the value in accumulator is strictly equal to null.
            IGNITION_HANDLER(TestNull, InterpreterAssembler)
            {
                Node* object = GetAccumulator();
                Node* result = SelectBooleanConstant(WordEqual(object, NullConstant()));
                SetAccumulator(result);
                Dispatch();
            }

            // TestUndefined
            //
            // Test if the value in the accumulator is strictly equal to undefined.
            IGNITION_HANDLER(TestUndefined, InterpreterAssembler)
            {
                Node* object = GetAccumulator();
                Node* result = SelectBooleanConstant(WordEqual(object, UndefinedConstant()));
                SetAccumulator(result);
                Dispatch();
            }

            // TestTypeOf <literal_flag>
            //
            // Tests if the object in the <accumulator> is typeof the literal represented
            // by |literal_flag|.
            IGNITION_HANDLER(TestTypeOf, InterpreterAssembler)
            {
                Node* object = GetAccumulator();
                Node* literal_flag = BytecodeOperandFlag(0);

#define MAKE_LABEL(name, lower_case) Label if_##lower_case(this);
                TYPEOF_LITERAL_LIST(MAKE_LABEL)
#undef MAKE_LABEL

#define LABEL_POINTER(name, lower_case) &if_##lower_case,
                Label* labels[] = { TYPEOF_LITERAL_LIST(LABEL_POINTER) };
#undef LABEL_POINTER

#define CASE(name, lower_case) \
    static_cast<int32_t>(TestTypeOfFlags::LiteralFlag::k##name),
                int32_t cases[] = { TYPEOF_LITERAL_LIST(CASE) };
#undef CASE

                Label if_true(this), if_false(this), end(this);

                // We juse use the final label as the default and properly CSA_ASSERT
                // that the {literal_flag} is valid here; this significantly improves
                // the generated code (compared to having a default label that aborts).
                unsigned const num_cases = arraysize(cases);
                CSA_ASSERT(this, Uint32LessThan(literal_flag, Int32Constant(num_cases)));
                Switch(literal_flag, labels[num_cases - 1], cases, labels, num_cases - 1);

                BIND(&if_number);
                {
                    Comment("IfNumber");
                    GotoIfNumber(object, &if_true);
                    Goto(&if_false);
                }
                BIND(&if_string);
                {
                    Comment("IfString");
                    GotoIf(TaggedIsSmi(object), &if_false);
                    Branch(IsString(object), &if_true, &if_false);
                }
                BIND(&if_symbol);
                {
                    Comment("IfSymbol");
                    GotoIf(TaggedIsSmi(object), &if_false);
                    Branch(IsSymbol(object), &if_true, &if_false);
                }
                BIND(&if_boolean);
                {
                    Comment("IfBoolean");
                    GotoIf(WordEqual(object, TrueConstant()), &if_true);
                    Branch(WordEqual(object, FalseConstant()), &if_true, &if_false);
                }
                BIND(&if_bigint);
                {
                    Comment("IfBigInt");
                    GotoIf(TaggedIsSmi(object), &if_false);
                    Branch(IsBigInt(object), &if_true, &if_false);
                }
                BIND(&if_undefined);
                {
                    Comment("IfUndefined");
                    GotoIf(TaggedIsSmi(object), &if_false);
                    // Check it is not null and the map has the undetectable bit set.
                    GotoIf(IsNull(object), &if_false);
                    Branch(IsUndetectableMap(LoadMap(object)), &if_true, &if_false);
                }
                BIND(&if_function);
                {
                    Comment("IfFunction");
                    GotoIf(TaggedIsSmi(object), &if_false);
                    // Check if callable bit is set and not undetectable.
                    Node* map_bitfield = LoadMapBitField(LoadMap(object));
                    Node* callable_undetectable = Word32And(map_bitfield, Int32Constant(Map::IsUndetectableBit::kMask | Map::IsCallableBit::kMask));
                    Branch(Word32Equal(callable_undetectable,
                               Int32Constant(Map::IsCallableBit::kMask)),
                        &if_true, &if_false);
                }
                BIND(&if_object);
                {
                    Comment("IfObject");
                    GotoIf(TaggedIsSmi(object), &if_false);

                    // If the object is null then return true.
                    GotoIf(IsNull(object), &if_true);

                    // Check if the object is a receiver type and is not undefined or callable.
                    Node* map = LoadMap(object);
                    GotoIfNot(IsJSReceiverMap(map), &if_false);
                    Node* map_bitfield = LoadMapBitField(map);
                    Node* callable_undetectable = Word32And(map_bitfield, Int32Constant(Map::IsUndetectableBit::kMask | Map::IsCallableBit::kMask));
                    Branch(Word32Equal(callable_undetectable, Int32Constant(0)), &if_true,
                        &if_false);
                }
                BIND(&if_other);
                {
                    // Typeof doesn't return any other string value.
                    Goto(&if_false);
                }

                BIND(&if_false);
                {
                    SetAccumulator(FalseConstant());
                    Goto(&end);
                }
                BIND(&if_true);
                {
                    SetAccumulator(TrueConstant());
                    Goto(&end);
                }
                BIND(&end);
                Dispatch();
            }

            // Jump <imm>
            //
            // Jump by the number of bytes represented by the immediate operand |imm|.
            IGNITION_HANDLER(Jump, InterpreterAssembler)
            {
                Node* relative_jump = BytecodeOperandUImmWord(0);
                Jump(relative_jump);
            }

            // JumpConstant <idx>
            //
            // Jump by the number of bytes in the Smi in the |idx| entry in the constant
            // pool.
            IGNITION_HANDLER(JumpConstant, InterpreterAssembler)
            {
                Node* relative_jump = LoadAndUntagConstantPoolEntryAtOperandIndex(0);
                Jump(relative_jump);
            }

            // JumpIfTrue <imm>
            //
            // Jump by the number of bytes represented by an immediate operand if the
            // accumulator contains true. This only works for boolean inputs, and
            // will misbehave if passed arbitrary input values.
            IGNITION_HANDLER(JumpIfTrue, InterpreterAssembler)
            {
                Node* accumulator = GetAccumulator();
                Node* relative_jump = BytecodeOperandUImmWord(0);
                CSA_ASSERT(this, TaggedIsNotSmi(accumulator));
                CSA_ASSERT(this, IsBoolean(accumulator));
                JumpIfWordEqual(accumulator, TrueConstant(), relative_jump);
            }

            // JumpIfTrueConstant <idx>
            //
            // Jump by the number of bytes in the Smi in the |idx| entry in the constant
            // pool if the accumulator contains true. This only works for boolean inputs,
            // and will misbehave if passed arbitrary input values.
            IGNITION_HANDLER(JumpIfTrueConstant, InterpreterAssembler)
            {
                Node* accumulator = GetAccumulator();
                Node* relative_jump = LoadAndUntagConstantPoolEntryAtOperandIndex(0);
                CSA_ASSERT(this, TaggedIsNotSmi(accumulator));
                CSA_ASSERT(this, IsBoolean(accumulator));
                JumpIfWordEqual(accumulator, TrueConstant(), relative_jump);
            }

            // JumpIfFalse <imm>
            //
            // Jump by the number of bytes represented by an immediate operand if the
            // accumulator contains false. This only works for boolean inputs, and
            // will misbehave if passed arbitrary input values.
            IGNITION_HANDLER(JumpIfFalse, InterpreterAssembler)
            {
                Node* accumulator = GetAccumulator();
                Node* relative_jump = BytecodeOperandUImmWord(0);
                CSA_ASSERT(this, TaggedIsNotSmi(accumulator));
                CSA_ASSERT(this, IsBoolean(accumulator));
                JumpIfWordEqual(accumulator, FalseConstant(), relative_jump);
            }

            // JumpIfFalseConstant <idx>
            //
            // Jump by the number of bytes in the Smi in the |idx| entry in the constant
            // pool if the accumulator contains false. This only works for boolean inputs,
            // and will misbehave if passed arbitrary input values.
            IGNITION_HANDLER(JumpIfFalseConstant, InterpreterAssembler)
            {
                Node* accumulator = GetAccumulator();
                Node* relative_jump = LoadAndUntagConstantPoolEntryAtOperandIndex(0);
                CSA_ASSERT(this, TaggedIsNotSmi(accumulator));
                CSA_ASSERT(this, IsBoolean(accumulator));
                JumpIfWordEqual(accumulator, FalseConstant(), relative_jump);
            }

            // JumpIfToBooleanTrue <imm>
            //
            // Jump by the number of bytes represented by an immediate operand if the object
            // referenced by the accumulator is true when the object is cast to boolean.
            IGNITION_HANDLER(JumpIfToBooleanTrue, InterpreterAssembler)
            {
                Node* value = GetAccumulator();
                Node* relative_jump = BytecodeOperandUImmWord(0);
                Label if_true(this), if_false(this);
                BranchIfToBooleanIsTrue(value, &if_true, &if_false);
                BIND(&if_true);
                Jump(relative_jump);
                BIND(&if_false);
                Dispatch();
            }

            // JumpIfToBooleanTrueConstant <idx>
            //
            // Jump by the number of bytes in the Smi in the |idx| entry in the constant
            // pool if the object referenced by the accumulator is true when the object is
            // cast to boolean.
            IGNITION_HANDLER(JumpIfToBooleanTrueConstant, InterpreterAssembler)
            {
                Node* value = GetAccumulator();
                Node* relative_jump = LoadAndUntagConstantPoolEntryAtOperandIndex(0);
                Label if_true(this), if_false(this);
                BranchIfToBooleanIsTrue(value, &if_true, &if_false);
                BIND(&if_true);
                Jump(relative_jump);
                BIND(&if_false);
                Dispatch();
            }

            // JumpIfToBooleanFalse <imm>
            //
            // Jump by the number of bytes represented by an immediate operand if the object
            // referenced by the accumulator is false when the object is cast to boolean.
            IGNITION_HANDLER(JumpIfToBooleanFalse, InterpreterAssembler)
            {
                Node* value = GetAccumulator();
                Node* relative_jump = BytecodeOperandUImmWord(0);
                Label if_true(this), if_false(this);
                BranchIfToBooleanIsTrue(value, &if_true, &if_false);
                BIND(&if_true);
                Dispatch();
                BIND(&if_false);
                Jump(relative_jump);
            }

            // JumpIfToBooleanFalseConstant <idx>
            //
            // Jump by the number of bytes in the Smi in the |idx| entry in the constant
            // pool if the object referenced by the accumulator is false when the object is
            // cast to boolean.
            IGNITION_HANDLER(JumpIfToBooleanFalseConstant, InterpreterAssembler)
            {
                Node* value = GetAccumulator();
                Node* relative_jump = LoadAndUntagConstantPoolEntryAtOperandIndex(0);
                Label if_true(this), if_false(this);
                BranchIfToBooleanIsTrue(value, &if_true, &if_false);
                BIND(&if_true);
                Dispatch();
                BIND(&if_false);
                Jump(relative_jump);
            }

            // JumpIfNull <imm>
            //
            // Jump by the number of bytes represented by an immediate operand if the object
            // referenced by the accumulator is the null constant.
            IGNITION_HANDLER(JumpIfNull, InterpreterAssembler)
            {
                Node* accumulator = GetAccumulator();
                Node* relative_jump = BytecodeOperandUImmWord(0);
                JumpIfWordEqual(accumulator, NullConstant(), relative_jump);
            }

            // JumpIfNullConstant <idx>
            //
            // Jump by the number of bytes in the Smi in the |idx| entry in the constant
            // pool if the object referenced by the accumulator is the null constant.
            IGNITION_HANDLER(JumpIfNullConstant, InterpreterAssembler)
            {
                Node* accumulator = GetAccumulator();
                Node* relative_jump = LoadAndUntagConstantPoolEntryAtOperandIndex(0);
                JumpIfWordEqual(accumulator, NullConstant(), relative_jump);
            }

            // JumpIfNotNull <imm>
            //
            // Jump by the number of bytes represented by an immediate operand if the object
            // referenced by the accumulator is not the null constant.
            IGNITION_HANDLER(JumpIfNotNull, InterpreterAssembler)
            {
                Node* accumulator = GetAccumulator();
                Node* relative_jump = BytecodeOperandUImmWord(0);
                JumpIfWordNotEqual(accumulator, NullConstant(), relative_jump);
            }

            // JumpIfNotNullConstant <idx>
            //
            // Jump by the number of bytes in the Smi in the |idx| entry in the constant
            // pool if the object referenced by the accumulator is not the null constant.
            IGNITION_HANDLER(JumpIfNotNullConstant, InterpreterAssembler)
            {
                Node* accumulator = GetAccumulator();
                Node* relative_jump = LoadAndUntagConstantPoolEntryAtOperandIndex(0);
                JumpIfWordNotEqual(accumulator, NullConstant(), relative_jump);
            }

            // JumpIfUndefined <imm>
            //
            // Jump by the number of bytes represented by an immediate operand if the object
            // referenced by the accumulator is the undefined constant.
            IGNITION_HANDLER(JumpIfUndefined, InterpreterAssembler)
            {
                Node* accumulator = GetAccumulator();
                Node* relative_jump = BytecodeOperandUImmWord(0);
                JumpIfWordEqual(accumulator, UndefinedConstant(), relative_jump);
            }

            // JumpIfUndefinedConstant <idx>
            //
            // Jump by the number of bytes in the Smi in the |idx| entry in the constant
            // pool if the object referenced by the accumulator is the undefined constant.
            IGNITION_HANDLER(JumpIfUndefinedConstant, InterpreterAssembler)
            {
                Node* accumulator = GetAccumulator();
                Node* relative_jump = LoadAndUntagConstantPoolEntryAtOperandIndex(0);
                JumpIfWordEqual(accumulator, UndefinedConstant(), relative_jump);
            }

            // JumpIfNotUndefined <imm>
            //
            // Jump by the number of bytes represented by an immediate operand if the object
            // referenced by the accumulator is not the undefined constant.
            IGNITION_HANDLER(JumpIfNotUndefined, InterpreterAssembler)
            {
                Node* accumulator = GetAccumulator();
                Node* relative_jump = BytecodeOperandUImmWord(0);
                JumpIfWordNotEqual(accumulator, UndefinedConstant(), relative_jump);
            }

            // JumpIfNotUndefinedConstant <idx>
            //
            // Jump by the number of bytes in the Smi in the |idx| entry in the constant
            // pool if the object referenced by the accumulator is not the undefined
            // constant.
            IGNITION_HANDLER(JumpIfNotUndefinedConstant, InterpreterAssembler)
            {
                Node* accumulator = GetAccumulator();
                Node* relative_jump = LoadAndUntagConstantPoolEntryAtOperandIndex(0);
                JumpIfWordNotEqual(accumulator, UndefinedConstant(), relative_jump);
            }

            // JumpIfJSReceiver <imm>
            //
            // Jump by the number of bytes represented by an immediate operand if the object
            // referenced by the accumulator is a JSReceiver.
            IGNITION_HANDLER(JumpIfJSReceiver, InterpreterAssembler)
            {
                Node* accumulator = GetAccumulator();
                Node* relative_jump = BytecodeOperandUImmWord(0);

                Label if_object(this), if_notobject(this, Label::kDeferred), if_notsmi(this);
                Branch(TaggedIsSmi(accumulator), &if_notobject, &if_notsmi);

                BIND(&if_notsmi);
                Branch(IsJSReceiver(accumulator), &if_object, &if_notobject);
                BIND(&if_object);
                Jump(relative_jump);

                BIND(&if_notobject);
                Dispatch();
            }

            // JumpIfJSReceiverConstant <idx>
            //
            // Jump by the number of bytes in the Smi in the |idx| entry in the constant
            // pool if the object referenced by the accumulator is a JSReceiver.
            IGNITION_HANDLER(JumpIfJSReceiverConstant, InterpreterAssembler)
            {
                Node* accumulator = GetAccumulator();
                Node* relative_jump = LoadAndUntagConstantPoolEntryAtOperandIndex(0);

                Label if_object(this), if_notobject(this), if_notsmi(this);
                Branch(TaggedIsSmi(accumulator), &if_notobject, &if_notsmi);

                BIND(&if_notsmi);
                Branch(IsJSReceiver(accumulator), &if_object, &if_notobject);

                BIND(&if_object);
                Jump(relative_jump);

                BIND(&if_notobject);
                Dispatch();
            }

            // JumpLoop <imm> <loop_depth>
            //
            // Jump by the number of bytes represented by the immediate operand |imm|. Also
            // performs a loop nesting check and potentially triggers OSR in case the
            // current OSR level matches (or exceeds) the specified |loop_depth|.
            IGNITION_HANDLER(JumpLoop, InterpreterAssembler)
            {
                Node* relative_jump = BytecodeOperandUImmWord(0);
                Node* loop_depth = BytecodeOperandImm(1);
                Node* osr_level = LoadOSRNestingLevel();

                // Check if OSR points at the given {loop_depth} are armed by comparing it to
                // the current {osr_level} loaded from the header of the BytecodeArray.
                Label ok(this), osr_armed(this, Label::kDeferred);
                Node* condition = Int32GreaterThanOrEqual(loop_depth, osr_level);
                Branch(condition, &ok, &osr_armed);

                BIND(&ok);
                JumpBackward(relative_jump);

                BIND(&osr_armed);
                {
                    Callable callable = CodeFactory::InterpreterOnStackReplacement(isolate());
                    Node* target = HeapConstant(callable.code());
                    Node* context = GetContext();
                    CallStub(callable.descriptor(), target, context);
                    JumpBackward(relative_jump);
                }
            }

            // SwitchOnSmiNoFeedback <table_start> <table_length> <case_value_base>
            //
            // Jump by the number of bytes defined by a Smi in a table in the constant pool,
            // where the table starts at |table_start| and has |table_length| entries.
            // The table is indexed by the accumulator, minus |case_value_base|. If the
            // case_value falls outside of the table |table_length|, fall-through to the
            // next bytecode.
            IGNITION_HANDLER(SwitchOnSmiNoFeedback, InterpreterAssembler)
            {
                Node* acc = GetAccumulator();
                Node* table_start = BytecodeOperandIdx(0);
                Node* table_length = BytecodeOperandUImmWord(1);
                Node* case_value_base = BytecodeOperandImmIntPtr(2);

                Label fall_through(this);

                // The accumulator must be a Smi.
                // TODO(leszeks): Add a bytecode with type feedback that allows other
                // accumulator values.
                CSA_ASSERT(this, TaggedIsSmi(acc));

                Node* case_value = IntPtrSub(SmiUntag(acc), case_value_base);
                GotoIf(IntPtrLessThan(case_value, IntPtrConstant(0)), &fall_through);
                GotoIf(IntPtrGreaterThanOrEqual(case_value, table_length), &fall_through);
                Node* entry = IntPtrAdd(table_start, case_value);
                Node* relative_jump = LoadAndUntagConstantPoolEntry(entry);
                Jump(relative_jump);

                BIND(&fall_through);
                Dispatch();
            }

            // CreateRegExpLiteral <pattern_idx> <literal_idx> <flags>
            //
            // Creates a regular expression literal for literal index <literal_idx> with
            // <flags> and the pattern in <pattern_idx>.
            IGNITION_HANDLER(CreateRegExpLiteral, InterpreterAssembler)
            {
                Node* pattern = LoadConstantPoolEntryAtOperandIndex(0);
                Node* feedback_vector = LoadFeedbackVector();
                Node* slot_id = BytecodeOperandIdx(1);
                Node* flags = SmiFromInt32(BytecodeOperandFlag(2));
                Node* context = GetContext();

                VARIABLE(result, MachineRepresentation::kTagged);

                ConstructorBuiltinsAssembler constructor_assembler(state());
                result.Bind(constructor_assembler.EmitCreateRegExpLiteral(
                    feedback_vector, slot_id, pattern, flags, context));
                SetAccumulator(result.value());
                Dispatch();
            }

            // CreateArrayLiteral <element_idx> <literal_idx> <flags>
            //
            // Creates an array literal for literal index <literal_idx> with
            // CreateArrayLiteral flags <flags> and constant elements in <element_idx>.
            IGNITION_HANDLER(CreateArrayLiteral, InterpreterAssembler)
            {
                Node* feedback_vector = LoadFeedbackVector();
                Node* slot_id = BytecodeOperandIdx(1);
                Node* context = GetContext();
                Node* bytecode_flags = BytecodeOperandFlag(2);

                Label fast_shallow_clone(this), call_runtime(this, Label::kDeferred);
                // No feedback, so handle it as a slow case.
                GotoIf(IsUndefined(feedback_vector), &call_runtime);

                Branch(IsSetWord32<CreateArrayLiteralFlags::FastCloneSupportedBit>(
                           bytecode_flags),
                    &fast_shallow_clone, &call_runtime);

                BIND(&fast_shallow_clone);
                {
                    ConstructorBuiltinsAssembler constructor_assembler(state());
                    Node* result = constructor_assembler.EmitCreateShallowArrayLiteral(
                        feedback_vector, slot_id, context, &call_runtime,
                        TRACK_ALLOCATION_SITE);
                    SetAccumulator(result);
                    Dispatch();
                }

                BIND(&call_runtime);
                {
                    Node* flags_raw = DecodeWordFromWord32<CreateArrayLiteralFlags::FlagsBits>(
                        bytecode_flags);
                    Node* flags = SmiTag(flags_raw);
                    Node* constant_elements = LoadConstantPoolEntryAtOperandIndex(0);
                    Node* result = CallRuntime(Runtime::kCreateArrayLiteral, context, feedback_vector,
                        SmiTag(slot_id), constant_elements, flags);
                    SetAccumulator(result);
                    Dispatch();
                }
            }

            // CreateEmptyArrayLiteral <literal_idx>
            //
            // Creates an empty JSArray literal for literal index <literal_idx>.
            IGNITION_HANDLER(CreateEmptyArrayLiteral, InterpreterAssembler)
            {
                Node* feedback_vector = LoadFeedbackVector();
                Node* slot_id = BytecodeOperandIdx(0);
                Node* context = GetContext();

                Label no_feedback(this, Label::kDeferred), end(this);
                VARIABLE(result, MachineRepresentation::kTagged);
                GotoIf(IsUndefined(feedback_vector), &no_feedback);

                ConstructorBuiltinsAssembler constructor_assembler(state());
                result.Bind(constructor_assembler.EmitCreateEmptyArrayLiteral(
                    feedback_vector, slot_id, context));
                Goto(&end);

                BIND(&no_feedback);
                {
                    TNode<Map> array_map = LoadJSArrayElementsMap(GetInitialFastElementsKind(),
                        LoadNativeContext(context));
                    result.Bind(AllocateJSArray(GetInitialFastElementsKind(), array_map,
                        SmiConstant(0), SmiConstant(0), nullptr,
                        ParameterMode::SMI_PARAMETERS));
                    Goto(&end);
                }

                BIND(&end);
                SetAccumulator(result.value());
                Dispatch();
            }

            // CreateArrayFromIterable
            //
            // Spread the given iterable from the accumulator into a new JSArray.
            // TODO(neis): Turn this into an intrinsic when we're running out of bytecodes.
            IGNITION_HANDLER(CreateArrayFromIterable, InterpreterAssembler)
            {
                Node* iterable = GetAccumulator();
                Node* context = GetContext();
                Node* result = CallBuiltin(Builtins::kIterableToListWithSymbolLookup, context, iterable);
                SetAccumulator(result);
                Dispatch();
            }

            // CreateObjectLiteral <element_idx> <literal_idx> <flags>
            //
            // Creates an object literal for literal index <literal_idx> with
            // CreateObjectLiteralFlags <flags> and constant elements in <element_idx>.
            IGNITION_HANDLER(CreateObjectLiteral, InterpreterAssembler)
            {
                Node* feedback_vector = LoadFeedbackVector();
                Node* slot_id = BytecodeOperandIdx(1);
                Node* bytecode_flags = BytecodeOperandFlag(2);

                Label if_fast_clone(this), if_not_fast_clone(this, Label::kDeferred);
                // No feedback, so handle it as a slow case.
                GotoIf(IsUndefined(feedback_vector), &if_not_fast_clone);

                // Check if we can do a fast clone or have to call the runtime.
                Branch(IsSetWord32<CreateObjectLiteralFlags::FastCloneSupportedBit>(
                           bytecode_flags),
                    &if_fast_clone, &if_not_fast_clone);

                BIND(&if_fast_clone);
                {
                    // If we can do a fast clone do the fast-path in CreateShallowObjectLiteral.
                    ConstructorBuiltinsAssembler constructor_assembler(state());
                    Node* result = constructor_assembler.EmitCreateShallowObjectLiteral(
                        feedback_vector, slot_id, &if_not_fast_clone);
                    SetAccumulator(result);
                    Dispatch();
                }

                BIND(&if_not_fast_clone);
                {
                    // If we can't do a fast clone, call into the runtime.
                    Node* object_boilerplate_description = LoadConstantPoolEntryAtOperandIndex(0);
                    Node* context = GetContext();

                    Node* flags_raw = DecodeWordFromWord32<CreateObjectLiteralFlags::FlagsBits>(
                        bytecode_flags);
                    Node* flags = SmiTag(flags_raw);

                    Node* result = CallRuntime(Runtime::kCreateObjectLiteral, context, feedback_vector,
                        SmiTag(slot_id), object_boilerplate_description, flags);
                    SetAccumulator(result);
                    // TODO(klaasb) build a single dispatch once the call is inlined
                    Dispatch();
                }
            }

            // CreateEmptyObjectLiteral
            //
            // Creates an empty JSObject literal.
            IGNITION_HANDLER(CreateEmptyObjectLiteral, InterpreterAssembler)
            {
                Node* context = GetContext();
                ConstructorBuiltinsAssembler constructor_assembler(state());
                Node* result = constructor_assembler.EmitCreateEmptyObjectLiteral(context);
                SetAccumulator(result);
                Dispatch();
            }

            // CloneObject <source_idx> <flags> <feedback_slot>
            //
            // Allocates a new JSObject with each enumerable own property copied from
            // {source}, converting getters into data properties.
            IGNITION_HANDLER(CloneObject, InterpreterAssembler)
            {
                Node* source = LoadRegisterAtOperandIndex(0);
                Node* bytecode_flags = BytecodeOperandFlag(1);
                Node* raw_flags = DecodeWordFromWord32<CreateObjectLiteralFlags::FlagsBits>(bytecode_flags);
                Node* smi_flags = SmiTag(raw_flags);
                Node* raw_slot = BytecodeOperandIdx(2);
                Node* smi_slot = SmiTag(raw_slot);
                Node* maybe_feedback_vector = LoadFeedbackVector();
                Node* context = GetContext();

                Variable var_result(this, MachineRepresentation::kTagged);
                var_result.Bind(CallBuiltin(Builtins::kCloneObjectIC, context, source,
                    smi_flags, smi_slot, maybe_feedback_vector));
                SetAccumulator(var_result.value());
                Dispatch();
            }

            // GetTemplateObject <descriptor_idx> <literal_idx>
            //
            // Creates the template to pass for tagged templates and returns it in the
            // accumulator, creating and caching the site object on-demand as per the
            // specification.
            IGNITION_HANDLER(GetTemplateObject, InterpreterAssembler)
            {
                Node* feedback_vector = LoadFeedbackVector();
                Node* slot = BytecodeOperandIdx(1);

                Label call_runtime(this, Label::kDeferred);
                GotoIf(IsUndefined(feedback_vector), &call_runtime);

                TNode<Object> cached_value = CAST(LoadFeedbackVectorSlot(feedback_vector, slot, 0, INTPTR_PARAMETERS));

                GotoIf(WordEqual(cached_value, SmiConstant(0)), &call_runtime);

                SetAccumulator(cached_value);
                Dispatch();

                BIND(&call_runtime);
                {
                    Node* description = LoadConstantPoolEntryAtOperandIndex(0);
                    Node* slot_smi = SmiTag(slot);
                    Node* closure = LoadRegister(Register::function_closure());
                    Node* shared_info = LoadObjectField(closure, JSFunction::kSharedFunctionInfoOffset);
                    Node* context = GetContext();
                    Node* result = CallRuntime(Runtime::kGetTemplateObject, context,
                        description, shared_info, slot_smi);

                    Label end(this);
                    GotoIf(IsUndefined(feedback_vector), &end);
                    StoreFeedbackVectorSlot(feedback_vector, slot, result);
                    Goto(&end);

                    Bind(&end);
                    SetAccumulator(result);
                    Dispatch();
                }
            }

            // CreateClosure <index> <slot> <flags>
            //
            // Creates a new closure for SharedFunctionInfo at position |index| in the
            // constant pool and with pretenuring controlled by |flags|.
            IGNITION_HANDLER(CreateClosure, InterpreterAssembler)
            {
                Node* shared = LoadConstantPoolEntryAtOperandIndex(0);
                Node* flags = BytecodeOperandFlag(2);
                Node* context = GetContext();
                Node* slot = BytecodeOperandIdx(1);

                Label if_undefined(this);
                TNode<FixedArray> feedback_cell_array = LoadClosureFeedbackArray(LoadRegister(Register::function_closure()));
                TNode<FeedbackCell> feedback_cell = CAST(LoadFixedArrayElement(feedback_cell_array, slot));

                Label if_fast(this), if_slow(this, Label::kDeferred);
                Branch(IsSetWord32<CreateClosureFlags::FastNewClosureBit>(flags), &if_fast,
                    &if_slow);

                BIND(&if_fast);
                {
                    Node* result = CallBuiltin(Builtins::kFastNewClosure, context, shared, feedback_cell);
                    SetAccumulator(result);
                    Dispatch();
                }

                BIND(&if_slow);
                {
                    Label if_newspace(this), if_oldspace(this);
                    Branch(IsSetWord32<CreateClosureFlags::PretenuredBit>(flags), &if_oldspace,
                        &if_newspace);

                    BIND(&if_newspace);
                    {
                        Node* result = CallRuntime(Runtime::kNewClosure, context, shared, feedback_cell);
                        SetAccumulator(result);
                        Dispatch();
                    }

                    BIND(&if_oldspace);
                    {
                        Node* result = CallRuntime(Runtime::kNewClosure_Tenured, context, shared,
                            feedback_cell);
                        SetAccumulator(result);
                        Dispatch();
                    }
                }
            }

            // CreateBlockContext <index>
            //
            // Creates a new block context with the scope info constant at |index|.
            IGNITION_HANDLER(CreateBlockContext, InterpreterAssembler)
            {
                Node* scope_info = LoadConstantPoolEntryAtOperandIndex(0);
                Node* context = GetContext();
                SetAccumulator(CallRuntime(Runtime::kPushBlockContext, context, scope_info));
                Dispatch();
            }

            // CreateCatchContext <exception> <scope_info_idx>
            //
            // Creates a new context for a catch block with the |exception| in a register
            // and the ScopeInfo at |scope_info_idx|.
            IGNITION_HANDLER(CreateCatchContext, InterpreterAssembler)
            {
                Node* exception = LoadRegisterAtOperandIndex(0);
                Node* scope_info = LoadConstantPoolEntryAtOperandIndex(1);
                Node* context = GetContext();
                SetAccumulator(
                    CallRuntime(Runtime::kPushCatchContext, context, exception, scope_info));
                Dispatch();
            }

            // CreateFunctionContext <scope_info_idx> <slots>
            //
            // Creates a new context with number of |slots| for the function closure.
            IGNITION_HANDLER(CreateFunctionContext, InterpreterAssembler)
            {
                Node* scope_info_idx = BytecodeOperandIdx(0);
                Node* scope_info = LoadConstantPoolEntry(scope_info_idx);
                Node* slots = BytecodeOperandUImm(1);
                Node* context = GetContext();
                ConstructorBuiltinsAssembler constructor_assembler(state());
                SetAccumulator(constructor_assembler.EmitFastNewFunctionContext(
                    scope_info, slots, context, FUNCTION_SCOPE));
                Dispatch();
            }

            // CreateEvalContext <scope_info_idx> <slots>
            //
            // Creates a new context with number of |slots| for an eval closure.
            IGNITION_HANDLER(CreateEvalContext, InterpreterAssembler)
            {
                Node* scope_info_idx = BytecodeOperandIdx(0);
                Node* scope_info = LoadConstantPoolEntry(scope_info_idx);
                Node* slots = BytecodeOperandUImm(1);
                Node* context = GetContext();
                ConstructorBuiltinsAssembler constructor_assembler(state());
                SetAccumulator(constructor_assembler.EmitFastNewFunctionContext(
                    scope_info, slots, context, EVAL_SCOPE));
                Dispatch();
            }

            // CreateWithContext <register> <scope_info_idx>
            //
            // Creates a new context with the ScopeInfo at |scope_info_idx| for a
            // with-statement with the object in |register|.
            IGNITION_HANDLER(CreateWithContext, InterpreterAssembler)
            {
                Node* object = LoadRegisterAtOperandIndex(0);
                Node* scope_info = LoadConstantPoolEntryAtOperandIndex(1);
                Node* context = GetContext();
                SetAccumulator(
                    CallRuntime(Runtime::kPushWithContext, context, object, scope_info));
                Dispatch();
            }

            // CreateMappedArguments
            //
            // Creates a new mapped arguments object.
            IGNITION_HANDLER(CreateMappedArguments, InterpreterAssembler)
            {
                Node* closure = LoadRegister(Register::function_closure());
                Node* context = GetContext();

                Label if_duplicate_parameters(this, Label::kDeferred);
                Label if_not_duplicate_parameters(this);

                // Check if function has duplicate parameters.
                // TODO(rmcilroy): Remove this check when FastNewSloppyArgumentsStub supports
                // duplicate parameters.
                Node* shared_info = LoadObjectField(closure, JSFunction::kSharedFunctionInfoOffset);
                Node* flags = LoadObjectField(shared_info, SharedFunctionInfo::kFlagsOffset,
                    MachineType::Uint32());
                Node* has_duplicate_parameters = IsSetWord32<SharedFunctionInfo::HasDuplicateParametersBit>(flags);
                Branch(has_duplicate_parameters, &if_duplicate_parameters,
                    &if_not_duplicate_parameters);

                BIND(&if_not_duplicate_parameters);
                {
                    ArgumentsBuiltinsAssembler constructor_assembler(state());
                    Node* result = constructor_assembler.EmitFastNewSloppyArguments(context, closure);
                    SetAccumulator(result);
                    Dispatch();
                }

                BIND(&if_duplicate_parameters);
                {
                    Node* result = CallRuntime(Runtime::kNewSloppyArguments_Generic, context, closure);
                    SetAccumulator(result);
                    Dispatch();
                }
            }

            // CreateUnmappedArguments
            //
            // Creates a new unmapped arguments object.
            IGNITION_HANDLER(CreateUnmappedArguments, InterpreterAssembler)
            {
                Node* context = GetContext();
                Node* closure = LoadRegister(Register::function_closure());
                ArgumentsBuiltinsAssembler builtins_assembler(state());
                Node* result = builtins_assembler.EmitFastNewStrictArguments(context, closure);
                SetAccumulator(result);
                Dispatch();
            }

            // CreateRestParameter
            //
            // Creates a new rest parameter array.
            IGNITION_HANDLER(CreateRestParameter, InterpreterAssembler)
            {
                Node* closure = LoadRegister(Register::function_closure());
                Node* context = GetContext();
                ArgumentsBuiltinsAssembler builtins_assembler(state());
                Node* result = builtins_assembler.EmitFastNewRestParameter(context, closure);
                SetAccumulator(result);
                Dispatch();
            }

            // StackCheck
            //
            // Performs a stack guard check.
            IGNITION_HANDLER(StackCheck, InterpreterAssembler)
            {
                TNode<Context> context = CAST(GetContext());
                PerformStackCheck(context);
                Dispatch();
            }

            // SetPendingMessage
            //
            // Sets the pending message to the value in the accumulator, and returns the
            // previous pending message in the accumulator.
            IGNITION_HANDLER(SetPendingMessage, InterpreterAssembler)
            {
                Node* pending_message = ExternalConstant(
                    ExternalReference::address_of_pending_message_obj(isolate()));
                Node* previous_message = Load(MachineType::TaggedPointer(), pending_message);
                Node* new_message = GetAccumulator();
                StoreFullTaggedNoWriteBarrier(pending_message, new_message);
                SetAccumulator(previous_message);
                Dispatch();
            }

            // Throw
            //
            // Throws the exception in the accumulator.
            IGNITION_HANDLER(Throw, InterpreterAssembler)
            {
                Node* exception = GetAccumulator();
                Node* context = GetContext();
                CallRuntime(Runtime::kThrow, context, exception);
                // We shouldn't ever return from a throw.
                Abort(AbortReason::kUnexpectedReturnFromThrow);
                Unreachable();
            }

            // ReThrow
            //
            // Re-throws the exception in the accumulator.
            IGNITION_HANDLER(ReThrow, InterpreterAssembler)
            {
                Node* exception = GetAccumulator();
                Node* context = GetContext();
                CallRuntime(Runtime::kReThrow, context, exception);
                // We shouldn't ever return from a throw.
                Abort(AbortReason::kUnexpectedReturnFromThrow);
                Unreachable();
            }

            // Abort <abort_reason>
            //
            // Aborts execution (via a call to the runtime function).
            IGNITION_HANDLER(Abort, InterpreterAssembler)
            {
                Node* reason = BytecodeOperandIdx(0);
                CallRuntime(Runtime::kAbort, NoContextConstant(), SmiTag(reason));
                Unreachable();
            }

            // Return
            //
            // Return the value in the accumulator.
            IGNITION_HANDLER(Return, InterpreterAssembler)
            {
                UpdateInterruptBudgetOnReturn();
                Node* accumulator = GetAccumulator();
                Return(accumulator);
            }

            // ThrowReferenceErrorIfHole <variable_name>
            //
            // Throws an exception if the value in the accumulator is TheHole.
            IGNITION_HANDLER(ThrowReferenceErrorIfHole, InterpreterAssembler)
            {
                Node* value = GetAccumulator();

                Label throw_error(this, Label::kDeferred);
                GotoIf(WordEqual(value, TheHoleConstant()), &throw_error);
                Dispatch();

                BIND(&throw_error);
                {
                    Node* name = LoadConstantPoolEntryAtOperandIndex(0);
                    CallRuntime(Runtime::kThrowAccessedUninitializedVariable, GetContext(),
                        name);
                    // We shouldn't ever return from a throw.
                    Abort(AbortReason::kUnexpectedReturnFromThrow);
                    Unreachable();
                }
            }

            // ThrowSuperNotCalledIfHole
            //
            // Throws an exception if the value in the accumulator is TheHole.
            IGNITION_HANDLER(ThrowSuperNotCalledIfHole, InterpreterAssembler)
            {
                Node* value = GetAccumulator();

                Label throw_error(this, Label::kDeferred);
                GotoIf(WordEqual(value, TheHoleConstant()), &throw_error);
                Dispatch();

                BIND(&throw_error);
                {
                    CallRuntime(Runtime::kThrowSuperNotCalled, GetContext());
                    // We shouldn't ever return from a throw.
                    Abort(AbortReason::kUnexpectedReturnFromThrow);
                    Unreachable();
                }
            }

            // ThrowSuperAlreadyCalledIfNotHole
            //
            // Throws SuperAleradyCalled exception if the value in the accumulator is not
            // TheHole.
            IGNITION_HANDLER(ThrowSuperAlreadyCalledIfNotHole, InterpreterAssembler)
            {
                Node* value = GetAccumulator();

                Label throw_error(this, Label::kDeferred);
                GotoIf(WordNotEqual(value, TheHoleConstant()), &throw_error);
                Dispatch();

                BIND(&throw_error);
                {
                    CallRuntime(Runtime::kThrowSuperAlreadyCalledError, GetContext());
                    // We shouldn't ever return from a throw.
                    Abort(AbortReason::kUnexpectedReturnFromThrow);
                    Unreachable();
                }
            }

            // Debugger
            //
            // Call runtime to handle debugger statement.
            IGNITION_HANDLER(Debugger, InterpreterAssembler)
            {
                Node* context = GetContext();
                CallStub(CodeFactory::HandleDebuggerStatement(isolate()), context);
                Dispatch();
            }

// DebugBreak
//
// Call runtime to handle a debug break.
#define DEBUG_BREAK(Name, ...)                                                                 \
    IGNITION_HANDLER(Name, InterpreterAssembler)                                               \
    {                                                                                          \
        Node* context = GetContext();                                                          \
        Node* accumulator = GetAccumulator();                                                  \
        Node* result_pair = CallRuntime(Runtime::kDebugBreakOnBytecode, context, accumulator); \
        Node* return_value = Projection(0, result_pair);                                       \
        Node* original_bytecode = SmiUntag(Projection(1, result_pair));                        \
        MaybeDropFrames(context);                                                              \
        SetAccumulator(return_value);                                                          \
        DispatchToBytecode(original_bytecode, BytecodeOffset());                               \
    }
            DEBUG_BREAK_BYTECODE_LIST(DEBUG_BREAK)
#undef DEBUG_BREAK

            // IncBlockCounter <slot>
            //
            // Increment the execution count for the given slot. Used for block code
            // coverage.
            IGNITION_HANDLER(IncBlockCounter, InterpreterAssembler)
            {
                Node* closure = LoadRegister(Register::function_closure());
                Node* coverage_array_slot = BytecodeOperandIdxSmi(0);
                Node* context = GetContext();

                CallRuntime(Runtime::kIncBlockCounter, context, closure, coverage_array_slot);

                Dispatch();
            }

            // ForInEnumerate <receiver>
            //
            // Enumerates the enumerable keys of the |receiver| and either returns the
            // map of the |receiver| if it has a usable enum cache or a fixed array
            // with the keys to enumerate in the accumulator.
            IGNITION_HANDLER(ForInEnumerate, InterpreterAssembler)
            {
                Node* receiver = LoadRegisterAtOperandIndex(0);
                Node* context = GetContext();

                Label if_empty(this), if_runtime(this, Label::kDeferred);
                Node* receiver_map = CheckEnumCache(receiver, &if_empty, &if_runtime);
                SetAccumulator(receiver_map);
                Dispatch();

                BIND(&if_empty);
                {
                    Node* result = EmptyFixedArrayConstant();
                    SetAccumulator(result);
                    Dispatch();
                }

                BIND(&if_runtime);
                {
                    Node* result = CallRuntime(Runtime::kForInEnumerate, context, receiver);
                    SetAccumulator(result);
                    Dispatch();
                }
            }

            // ForInPrepare <cache_info_triple>
            //
            // Returns state for for..in loop execution based on the enumerator in
            // the accumulator register, which is the result of calling ForInEnumerate
            // on a JSReceiver object.
            // The result is output in registers |cache_info_triple| to
            // |cache_info_triple + 2|, with the registers holding cache_type, cache_array,
            // and cache_length respectively.
            IGNITION_HANDLER(ForInPrepare, InterpreterAssembler)
            {
                Node* enumerator = GetAccumulator();
                Node* vector_index = BytecodeOperandIdx(1);
                Node* maybe_feedback_vector = LoadFeedbackVector();

                // The {enumerator} is either a Map or a FixedArray.
                CSA_ASSERT(this, TaggedIsNotSmi(enumerator));

                // Check if we're using an enum cache.
                Label if_fast(this), if_slow(this);
                Branch(IsMap(enumerator), &if_fast, &if_slow);

                BIND(&if_fast);
                {
                    // Load the enumeration length and cache from the {enumerator}.
                    Node* enum_length = LoadMapEnumLength(enumerator);
                    CSA_ASSERT(this, WordNotEqual(enum_length, IntPtrConstant(kInvalidEnumCacheSentinel)));
                    Node* descriptors = LoadMapDescriptors(enumerator);
                    Node* enum_cache = LoadObjectField(descriptors, DescriptorArray::kEnumCacheOffset);
                    Node* enum_keys = LoadObjectField(enum_cache, EnumCache::kKeysOffset);

                    // Check if we have enum indices available.
                    Node* enum_indices = LoadObjectField(enum_cache, EnumCache::kIndicesOffset);
                    Node* enum_indices_length = LoadAndUntagFixedArrayBaseLength(enum_indices);
                    Node* feedback = SelectSmiConstant(
                        IntPtrLessThanOrEqual(enum_length, enum_indices_length),
                        ForInFeedback::kEnumCacheKeysAndIndices, ForInFeedback::kEnumCacheKeys);
                    UpdateFeedback(feedback, maybe_feedback_vector, vector_index);

                    // Construct the cache info triple.
                    Node* cache_type = enumerator;
                    Node* cache_array = enum_keys;
                    Node* cache_length = SmiTag(enum_length);
                    StoreRegisterTripleAtOperandIndex(cache_type, cache_array, cache_length, 0);
                    Dispatch();
                }

                BIND(&if_slow);
                {
                    // The {enumerator} is a FixedArray with all the keys to iterate.
                    CSA_ASSERT(this, IsFixedArray(enumerator));

                    // Record the fact that we hit the for-in slow-path.
                    UpdateFeedback(SmiConstant(ForInFeedback::kAny), maybe_feedback_vector,
                        vector_index);

                    // Construct the cache info triple.
                    Node* cache_type = enumerator;
                    Node* cache_array = enumerator;
                    Node* cache_length = LoadFixedArrayBaseLength(enumerator);
                    StoreRegisterTripleAtOperandIndex(cache_type, cache_array, cache_length, 0);
                    Dispatch();
                }
            }

            // ForInNext <receiver> <index> <cache_info_pair>
            //
            // Returns the next enumerable property in the the accumulator.
            IGNITION_HANDLER(ForInNext, InterpreterAssembler)
            {
                Node* receiver = LoadRegisterAtOperandIndex(0);
                Node* index = LoadRegisterAtOperandIndex(1);
                Node* cache_type;
                Node* cache_array;
                std::tie(cache_type, cache_array) = LoadRegisterPairAtOperandIndex(2);
                Node* vector_index = BytecodeOperandIdx(3);
                Node* maybe_feedback_vector = LoadFeedbackVector();

                // Load the next key from the enumeration array.
                Node* key = LoadFixedArrayElement(CAST(cache_array), index, 0,
                    CodeStubAssembler::SMI_PARAMETERS);

                // Check if we can use the for-in fast path potentially using the enum cache.
                Label if_fast(this), if_slow(this, Label::kDeferred);
                Node* receiver_map = LoadMap(receiver);
                Branch(WordEqual(receiver_map, cache_type), &if_fast, &if_slow);
                BIND(&if_fast);
                {
                    // Enum cache in use for {receiver}, the {key} is definitely valid.
                    SetAccumulator(key);
                    Dispatch();
                }
                BIND(&if_slow);
                {
                    // Record the fact that we hit the for-in slow-path.
                    UpdateFeedback(SmiConstant(ForInFeedback::kAny), maybe_feedback_vector,
                        vector_index);

                    // Need to filter the {key} for the {receiver}.
                    Node* context = GetContext();
                    Node* result = CallBuiltin(Builtins::kForInFilter, context, key, receiver);
                    SetAccumulator(result);
                    Dispatch();
                }
            }

            // ForInContinue <index> <cache_length>
            //
            // Returns false if the end of the enumerable properties has been reached.
            IGNITION_HANDLER(ForInContinue, InterpreterAssembler)
            {
                Node* index = LoadRegisterAtOperandIndex(0);
                Node* cache_length = LoadRegisterAtOperandIndex(1);

                // Check if {index} is at {cache_length} already.
                Label if_true(this), if_false(this), end(this);
                Branch(WordEqual(index, cache_length), &if_true, &if_false);
                BIND(&if_true);
                {
                    SetAccumulator(FalseConstant());
                    Goto(&end);
                }
                BIND(&if_false);
                {
                    SetAccumulator(TrueConstant());
                    Goto(&end);
                }
                BIND(&end);
                Dispatch();
            }

            // ForInStep <index>
            //
            // Increments the loop counter in register |index| and stores the result
            // in the accumulator.
            IGNITION_HANDLER(ForInStep, InterpreterAssembler)
            {
                TNode<Smi> index = CAST(LoadRegisterAtOperandIndex(0));
                TNode<Smi> one = SmiConstant(1);
                TNode<Smi> result = SmiAdd(index, one);
                SetAccumulator(result);
                Dispatch();
            }

            // Wide
            //
            // Prefix bytecode indicating next bytecode has wide (16-bit) operands.
            IGNITION_HANDLER(Wide, InterpreterAssembler)
            {
                DispatchWide(OperandScale::kDouble);
            }

            // ExtraWide
            //
            // Prefix bytecode indicating next bytecode has extra-wide (32-bit) operands.
            IGNITION_HANDLER(ExtraWide, InterpreterAssembler)
            {
                DispatchWide(OperandScale::kQuadruple);
            }

            // Illegal
            //
            // An invalid bytecode aborting execution if dispatched.
            IGNITION_HANDLER(Illegal, InterpreterAssembler)
            {
                Abort(AbortReason::kInvalidBytecode);
                Unreachable();
            }

            // SuspendGenerator <generator> <first input register> <register count>
            // <suspend_id>
            //
            // Stores the parameters and the register file in the generator. Also stores
            // the current context, |suspend_id|, and the current bytecode offset
            // (for debugging purposes) into the generator. Then, returns the value
            // in the accumulator.
            IGNITION_HANDLER(SuspendGenerator, InterpreterAssembler)
            {
                Node* generator = LoadRegisterAtOperandIndex(0);
                TNode<FixedArray> array = CAST(LoadObjectField(
                    generator, JSGeneratorObject::kParametersAndRegistersOffset));
                Node* closure = LoadRegister(Register::function_closure());
                Node* context = GetContext();
                RegListNodePair registers = GetRegisterListAtOperandIndex(1);
                Node* suspend_id = BytecodeOperandUImmSmi(3);

                Node* shared = LoadObjectField(closure, JSFunction::kSharedFunctionInfoOffset);
                TNode<Int32T> formal_parameter_count = UncheckedCast<Int32T>(
                    LoadObjectField(shared, SharedFunctionInfo::kFormalParameterCountOffset,
                        MachineType::Uint16()));

                ExportParametersAndRegisterFile(array, registers, formal_parameter_count);
                StoreObjectField(generator, JSGeneratorObject::kContextOffset, context);
                StoreObjectField(generator, JSGeneratorObject::kContinuationOffset,
                    suspend_id);

                // Store the bytecode offset in the [input_or_debug_pos] field, to be used by
                // the inspector.
                Node* offset = SmiTag(BytecodeOffset());
                StoreObjectField(generator, JSGeneratorObject::kInputOrDebugPosOffset,
                    offset);

                UpdateInterruptBudgetOnReturn();
                Return(GetAccumulator());
            }

            // SwitchOnGeneratorState <generator> <table_start> <table_length>
            //
            // If |generator| is undefined, falls through. Otherwise, loads the
            // generator's state (overwriting it with kGeneratorExecuting), sets the context
            // to the generator's resume context, and performs state dispatch on the
            // generator's state by looking up the generator state in a jump table in the
            // constant pool, starting at |table_start|, and of length |table_length|.
            IGNITION_HANDLER(SwitchOnGeneratorState, InterpreterAssembler)
            {
                Node* generator = LoadRegisterAtOperandIndex(0);

                Label fallthrough(this);
                GotoIf(WordEqual(generator, UndefinedConstant()), &fallthrough);

                Node* state = LoadObjectField(generator, JSGeneratorObject::kContinuationOffset);
                Node* new_state = SmiConstant(JSGeneratorObject::kGeneratorExecuting);
                StoreObjectField(generator, JSGeneratorObject::kContinuationOffset,
                    new_state);

                Node* context = LoadObjectField(generator, JSGeneratorObject::kContextOffset);
                SetContext(context);

                Node* table_start = BytecodeOperandIdx(1);
                // TODO(leszeks): table_length is only used for a CSA_ASSERT, we don't
                // actually need it otherwise.
                Node* table_length = BytecodeOperandUImmWord(2);

                // The state must be a Smi.
                CSA_ASSERT(this, TaggedIsSmi(state));

                Node* case_value = SmiUntag(state);

                CSA_ASSERT(this, IntPtrGreaterThanOrEqual(case_value, IntPtrConstant(0)));
                CSA_ASSERT(this, IntPtrLessThan(case_value, table_length));
                USE(table_length);

                Node* entry = IntPtrAdd(table_start, case_value);
                Node* relative_jump = LoadAndUntagConstantPoolEntry(entry);
                Jump(relative_jump);

                BIND(&fallthrough);
                Dispatch();
            }

            // ResumeGenerator <generator> <first output register> <register count>
            //
            // Imports the register file stored in the generator and marks the generator
            // state as executing.
            IGNITION_HANDLER(ResumeGenerator, InterpreterAssembler)
            {
                Node* generator = LoadRegisterAtOperandIndex(0);
                Node* closure = LoadRegister(Register::function_closure());
                RegListNodePair registers = GetRegisterListAtOperandIndex(1);

                Node* shared = LoadObjectField(closure, JSFunction::kSharedFunctionInfoOffset);
                TNode<Int32T> formal_parameter_count = UncheckedCast<Int32T>(
                    LoadObjectField(shared, SharedFunctionInfo::kFormalParameterCountOffset,
                        MachineType::Uint16()));

                ImportRegisterFile(
                    CAST(LoadObjectField(generator,
                        JSGeneratorObject::kParametersAndRegistersOffset)),
                    registers, formal_parameter_count);

                // Return the generator's input_or_debug_pos in the accumulator.
                SetAccumulator(
                    LoadObjectField(generator, JSGeneratorObject::kInputOrDebugPosOffset));

                Dispatch();
            }

        } // namespace

        Handle<Code> GenerateBytecodeHandler(Isolate* isolate, Bytecode bytecode,
            OperandScale operand_scale,
            int builtin_index,
            const AssemblerOptions& options)
        {
            Zone zone(isolate->allocator(), ZONE_NAME);
            compiler::CodeAssemblerState state(
                isolate, &zone, InterpreterDispatchDescriptor {}, Code::BYTECODE_HANDLER,
                Bytecodes::ToString(bytecode),
                FLAG_untrusted_code_mitigations
                    ? PoisoningMitigationLevel::kPoisonCriticalOnly
                    : PoisoningMitigationLevel::kDontPoison,
                builtin_index);

            switch (bytecode) {
#define CALL_GENERATOR(Name, ...)                         \
    case Bytecode::k##Name:                               \
        Name##Assembler::Generate(&state, operand_scale); \
        break;
                BYTECODE_LIST(CALL_GENERATOR);
#undef CALL_GENERATOR
            }

            Handle<Code> code = compiler::CodeAssembler::GenerateCode(&state, options);
            PROFILE(isolate, CodeCreateEvent(CodeEventListener::BYTECODE_HANDLER_TAG, AbstractCode::cast(*code), Bytecodes::ToString(bytecode, operand_scale).c_str()));
#ifdef ENABLE_DISASSEMBLER
            if (FLAG_trace_ignition_codegen) {
                StdoutStream os;
                code->Disassemble(Bytecodes::ToString(bytecode), os);
                os << std::flush;
            }
#endif // ENABLE_DISASSEMBLER
            return code;
        }

    } // namespace interpreter
} // namespace internal
} // namespace v8
